I 've been trying to implement the RadialProgress component (https://components.xamarin.com/view/radialprogress) on my app.
I managed to get it on the screen, and change the progress colour but I can't find a way to change the inner colour of the circle.
The RadialProgressView object itself has a BackgroundTintMode field which takes a DuffPorter.Mode but whenever I try to set the background tint mode the app breaks with this message (Message = "no method with name='setBackgroundTintMode' signature='(Landroid/graphics/PorterDuff$Mode;))
Is there even a way to do what I want?
Thanks!
Yes, it can be done. Although not in a very straight-forward way or even maintainable way.
Firstly, let's dig a little into RadialProgressView's drawing code (as exposed by Xamarin Studio Assembly Browser):
protected override void OnDraw(Canvas canvas)
{
// ... there's more stuff here, but you get the idea
canvas.DrawCircle(this.bgCx, this.bgCy, this.radius, this.bgCirclePaint);
canvas.DrawCircle(this.bgCx, this.bgCy, this.innerLineRadius, this.bgBorderPaint);
canvas.DrawText(this.valueText, this.textX, this.textY, this.textPaint);
}
We notice some colors here, like bgCirclePaintand bgBorderPaint. If we are able to change the value of these variables, we will be able to change the color with which the ProgressView is painted.
The problem is that RadialProgressView does not expose the fields – they are all private, so simply inheriting from RadialProgressView will not allow us to set them to a new value.
However, we can make use of reflection to change these private fields, like so:
var textPaintMember = typeof(RadialProgressView).GetField("textPaint", BindingFlags.Instance | BindingFlags.NonPublic);
textPaintMember.SetValue(Instance, MyNewSuperCoolColorPaint);
By combining the two, we can come up with a new, customizable class like this:
public class CustomizableRadialProgressView : RadialProgressView
{
public CustomizableRadialProgressView(Context context) : base(context)
{
}
public void SetTextColor(Color color)
{
var paint = new Paint();
paint.SetTypeface(Typeface.DefaultBold);
paint.Color = color;
paint.AntiAlias = true;
var textPaintMember = typeof(RadialProgressView).GetField("textPaint", BindingFlags.Instance | BindingFlags.NonPublic);
textPaintMember.SetValue(this, paint);
}
public void SetCircleColor(Color color)
{
var paint = new Paint();
paint.SetStyle(Paint.Style.Fill);
paint.Color = color;
paint.AntiAlias = true;
var circlePaintMember = typeof(RadialProgressView).GetField("bgCirclePaint", BindingFlags.Instance | BindingFlags.NonPublic);
circlePaintMember.SetValue(this, paint);
}
public void SetBorderColor(Color color)
{
var paint = new Paint();
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
paint.AntiAlias = true;
var circlePaintMember = typeof(RadialProgressView).GetField("bgBorderPaint", BindingFlags.Instance | BindingFlags.NonPublic);
circlePaintMember.SetValue(this, paint);
}
public void SetProgressPackgroundColor(Color color)
{
var paint = new Paint();
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
paint.AntiAlias = true;
var circlePaintMember = typeof(RadialProgressView).GetField("bgProgressPaint", BindingFlags.Instance | BindingFlags.NonPublic);
circlePaintMember.SetValue(this, paint);
}
}
This will get us the result we're after:
Note: It is probably wise to notice that we are making improper use of private fields: We're manipulating them from outside of the class they live in. If Xamarin ever decides to change the way RadialProgressViewis implemented, or even only renames one of the private variables, our code will fail at runtime. The better way to approach this problem would probably be to just ask Xamarin to provide the getters/setters you need. But, hey, it's SO much cooler this way ;)
You can try implementing a custom ViewRenderer and access the underlying native Android views to modify them as you want.
https://blog.xamarin.com/using-custom-controls-in-xamarin.forms-on-android/
The error you have regarding the "setBackgroundTintMode" method indicates that you may need to update your Xamarin platform to make sure that the latest APIs are available
Related
I am trying to draw a line chart using canvas.drawLines(...), but it seems that the lines are not properly connected. As I understand using Paint.setStrokeJoin should use the miter join:
chartLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
chartLinePaint.setStyle(Paint.Style.STROKE);
chartLinePaint.setStrokeJoin(Paint.Join.MITER);
chartLinePaint.setStrokeWidth(6.0f);
How do I fix this problem and make the lines properly joined?
As I told you in the comment, Paint objects are fully applied only when you draw them with Path.
In drawLine documentation there is a paragraph with: 'the Style is ignored in the paint' and the same thing is applied to drawLines method.
To test this, I created a simple custom view:
class CanvasTestView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val textPaint1 = Paint(ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeJoin = Paint.Join.MITER
strokeWidth = 12.0f
color = Color.RED
}
private val textPaint2 = Paint(ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeJoin = Paint.Join.MITER
strokeWidth = 12.0f
color = Color.BLUE
}
#SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
val floatArray = floatArrayOf(250f, 550f, 450f, 200f, 450f, 200f, 650f, 700f)
drawLines(floatArray, textPaint2)
val path = Path()
path.moveTo(200f, 500f)
path.lineTo(400f, 200f)
path.lineTo(600f, 700f)
drawPath(path, textPaint1)
}
}
}
And the result is this:
So using drawLines partially apply the styles of Paint obj, like colours, but is not applying strokeJoin like properties. drawPath seems to apply all of them instead.
If you have a performance problem maybe you can try to cache the result somewhere, pre-compute the animation or try with a simpler one.
Remember that if you don't have particular requirements there is this
awesome library: MPAndroidChart which already has some built-in animations
Problem
As you may have noticed in the Android documentation, you can’t apply
a style to a drawLine.
ref
From the Canvas documentation
drawLine:
public void drawLine (float startX, float startY, float stopX, float stopY, Paint paint)
Draw a line segment with the specified start and stop x,y coordinates,
using the specified paint.
Note that since a line is always "framed", the Style is ignored in the
paint.
Degenerate lines (length is 0) will not be drawn.
drawLines:
public void drawLines (float[] pts, int offset, int count, Paint paint)
Draw a series of lines. Each line is taken from 4 consecutive values
in the pts array. Thus to draw 1 line, the array must contain at least
4 values. This is logically the same as drawing the array as
follows:
drawLine(pts[0], pts[1], pts[2], pts[3])
followed by:
drawLine(pts[4], pts[5], pts[6], pts[7])
and so on.
Solution
If you need to apply a style, the solution is to use drawPath instead.
It will apply the style set in the paint object.
For anyone else looking for to use the StrokeJoin or StrokeCap with DrawPath(), you can try SkPathEffect such as CreateCorner():
MS Docs, SkiaSharp, but kind of the same.
I am using https://github.com/jjoe64/GraphView for a real-time android graph application. I am using 4.1.0 library version. I am using graph view to display a parameter (battery voltage) and change the color of line graph based on a certain value. For example, if I have < 12.0 Volts I want to change the line color to RED, and if its <12.5 Volts I want it to be yellow, etc, etc.
The trouble I have is the color of the entire lineGraph changes to the most recent value. I want the line to consist the appropriate color based on the Voltage.I do not wish to use PointGraph, I have been finding a lot of answers around that but that's beyond the scope of my application.
I am using the following code:
LineGraphSeries<DataPoint> series = new LineGraphSeries<DataPoint>(new DataPoint[]{});
Paint paint = new Paint();
double graph2LastXValue = 5d;
// real time logic which gets voltageAtLocation
graph2LastXValue += 1d;
if (voltageAtLocation > 12.50) {
paint.setColor(Color.GREEN);
} else if (voltageAtLocation > 12.00) {
paint.setColor(Color.YELLOW);
}
else {
paint.setColor(Color.RED);
}
series.appendData(new DataPoint(graph2LastXValue, voltageAtLocation), true, 100);
series.setCustomPaint(paint);
Has anyone looked into implementing the Loading images pattern from Google's latest Material Design guide.
It's a recommended way that "illustrations and photographs may load and transition in three phases at staggered durations". Those being Opacity, Exposure and Saturation:
I'm currently using the Volley NetworkImageView (actually a derived class from this).
I'm sure it's got to be some variant of the answer to this question. I'm just not sure which classes/code to use for both the saturation and animation curves that are described.
Thanks to #mttmllns! Previous Answer.
Since the previous answer shows an example written in C# and I was curious, I ported it to java. Complete GitHub Example
It outlines a 3-steps process where a combination of opacity, contrast/luminosity and saturation is used in concert to help salvage our poor users eyesight.
For a detailed explanation read this article.
EDIT:
See, the excellent answer provided by #DavidCrawford.
BTW: I fixed the linked GitHubProject to support pre-Lollipop devices. (Since API Level 11)
The Code
AlphaSatColorMatrixEvaluator.java
import android.animation.TypeEvaluator;
import android.graphics.ColorMatrix;
public class AlphaSatColorMatrixEvaluator implements TypeEvaluator {
private ColorMatrix colorMatrix;
float[] elements = new float[20];
public AlphaSatColorMatrixEvaluator() {
colorMatrix = new ColorMatrix ();
}
public ColorMatrix getColorMatrix() {
return colorMatrix;
}
#Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// There are 3 phases so we multiply fraction by that amount
float phase = fraction * 3;
// Compute the alpha change over period [0, 2]
float alpha = Math.min(phase, 2f) / 2f;
// elements [19] = (float)Math.round(alpha * 255);
elements [18] = alpha;
// We substract to make the picture look darker, it will automatically clamp
// This is spread over period [0, 2.5]
final int MaxBlacker = 100;
float blackening = (float)Math.round((1 - Math.min(phase, 2.5f) / 2.5f) * MaxBlacker);
elements [4] = elements [9] = elements [14] = -blackening;
// Finally we desaturate over [0, 3], taken from ColorMatrix.SetSaturation
float invSat = 1 - Math.max(0.2f, fraction);
float R = 0.213f * invSat;
float G = 0.715f * invSat;
float B = 0.072f * invSat;
elements[0] = R + fraction; elements[1] = G; elements[2] = B;
elements[5] = R; elements[6] = G + fraction; elements[7] = B;
elements[10] = R; elements[11] = G; elements[12] = B + fraction;
colorMatrix.set(elements);
return colorMatrix;
}
}
Here is how you can set it up:
ImageView imageView = (ImageView)findViewById(R.id.imageView);
final BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.image);
imageView.setImageDrawable(drawable);
AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator ();
final ColorMatrixColorFilter filter = new ColorMatrixColorFilter(evaluator.getColorMatrix());
drawable.setColorFilter(filter);
ObjectAnimator animator = ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
drawable.setColorFilter (filter);
}
});
animator.setDuration(1500);
animator.start();
And here is the result:
Please note that this answer, as it stands, works for Lollipop only. The reason for this is because the colorMatrix property is not available to animate on the ColorMatrixColorFilter class (it doesn't provide getColorMatrix and setColorMatrix methods). To see this in action, try the code, in logcat output you should see a warning message like this:
Method setColorMatrix() with type class android.graphics.ColorMatrix not found on target class class android.graphics.ColorMatrixColorFilter
That being said, I was able to get this to work on older android versions (pre-Lollipop) by creating the following class (not the best name, I know)
private class AnimateColorMatrixColorFilter {
private ColorMatrixColorFilter mFilter;
private ColorMatrix mMatrix;
public AnimateColorMatrixColorFilter(ColorMatrix matrix) {
setColorMatrix(matrix);
}
public ColorMatrixColorFilter getColorFilter() {
return mFilter;
}
public void setColorMatrix(ColorMatrix matrix) {
mMatrix = matrix;
mFilter = new ColorMatrixColorFilter(matrix);
}
public ColorMatrix getColorMatrix() {
return mMatrix;
}
}
Then, the setup code would look something like the following. Note that I have this "setup" in a derived class from ImageView and so I'm doing this in the overriden method setImageBitmap.
#Override
public void setImageBitmap(Bitmap bm) {
final Drawable drawable = new BitmapDrawable(getContext().getResources(), bm);
setImageDrawable(drawable);
AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator();
final AnimateColorMatrixColorFilter filter = new AnimateColorMatrixColorFilter(evaluator.getColorMatrix());
drawable.setColorFilter(filter.getColorFilter());
ObjectAnimator animator = ObjectAnimator.ofObject(filter, "colorMatrix", evaluator, evaluator.getColorMatrix());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
drawable.setColorFilter(filter.getColorFilter());
}
});
animator.setDuration(1500);
animator.start();
}
Following up on rnrneverdies's excellent answer, I'd like to offer a small fix to this animation logic.
My problem with this implementation is when it comes to png images with transparency (for example, circular images, or custom shapes). For these images, the colour filter will draw the transparency of the image as black, rather than just leaving them transparent.
The problem is with this line:
elements [19] = (float)Math.round(alpha * 255);
What's happening here is that the colour matrix is telling the bitmap that the alpha value of each pixels is equal to the current phase of the alpha. This is obviously not perfect, since pixels which were already transparent will lose their transparency and appear as black.
To fix this, instead of applying the alpha of the "additive" alpha field in the colour matrix, apply it on the "multiplicative" field:
Rm | 0 | 0 | 0 | Ra
0 | Gm | 0 | 0 | Ga
0 | 0 | Bm | 0 | Ba
0 | 0 | 0 | Am | Aa
Xm = multiplicative field
Xa = additive field
So instead of applying the alpha value on the "Aa" field (elements[19]), apply it on the "Am" field (elements[18]), and use the 0-1 value rather than the 0-255 value:
//elements [19] = (float)Math.round(alpha * 255);
elements [18] = alpha;
Now the transition will multiply the original alpha value of the bitmap with the alpha phase of the animation and not force an alpha value when there shouldn't be one.
Hope this helps
Was just wondering this same thing. I found a blog post detailing how to go about it with an example written in Xamarin:
http://blog.neteril.org/blog/2014/11/23/android-material-image-loading/
The gist for someone writing in Java: use ColorMatrix and ColorMatrixColorFilter.
The post also mentions an important optimization: using the Lollipop hidden setColorMatrix() API to avoid GC and GPU churn.
Have you seen it in use in any Google apps yet? If you end up implementing it I'd love to see your source.
I have a problem, I have tried resolve the problem but I haven't found a solution.
I have two columns of images. I want to join them through the midpoint of each image. The problem I have is that the attachment point moves down, like the image
I have a "main" class and I have the internal class: public class DrawView extends LinearLayout
with the atribute:
private Paint paint = new Paint();
and I set the next values:
paint.setColor(Color.BLACK);
paint.setStrokeWidth(6);
I use the next code for draw the lines:
public void onDraw(Canvas canvas) {
}
#SuppressLint("UseValueOf")
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (activateDraw) {
for (int i = 0; i < 5; i++) {
//I not include the color selection.
x1= Image[i].x + Image[i].width;
y1=Image[i].y+ (new Double(Image[i].height / 2).intValue()));
x2=ImagePr[i].x;
y2=ImagePr[i].y + (new Double((ImagePr[i].height) / 2).intValue()));
canvas.drawLine(x1, y1, x2, y2, paint);
}
activateDraw = false;
}
}
To set the x and y values I use the method:
public void setData(ImageView img) {
image = img;
int[] values = new int[2];
image.getLocationInWindow(values);
x = values[0];
y = values[1];
width = image.getWidth();
height = image.getHeight();
}
In the main class I have the atribute:
Canvas auxCanvas = new Canvas();
and I execute the onDraw(auxCanvas) method when I want draw the lines. Why the lines don't draw joining the "midpoints"?
Anyone can help me?Thanks!!
#Shaunak Sorry, it was a fail. I've removed it and it doesn't affect, the problem continues. Thank you!
#anthropomo I tried your change but the problem continues.
I don't understand why in the emulator seems to work fine, but not on the device.
SOLUTION:
(I thought I had written the answer, sorry)
The solution was very simple. The app is destinated to students that have 6-8 years, so I decided to hide the status bar and the above code works perfect without do changes!
Hide the status bar:
Hide Notification bar
How to hide the title bar for an Activity in XML with existing custom theme
If other people want to show the status bar, I suppose you need to subtract the status bar height.
reference: http://developer.android.com/reference/android/util/DisplayMetrics.html#density
does something like this work for you?:
float d = getResources().getDisplayMetrics().density;
canvas.drawLine(x1*d, y1*d, x2*d, y2*d, paint);
note: if the multiplication doesn't work try dividing by d... i can never remember what to do.
I am fairly new to Android, but why isn't my image showing on the canvas?
I know that it is working properly because the background color is black, which I changed in the same same method, onDraw . Can anyone help me? Thanks in advance!
public PongView(Context context) {
super(context);
paddle1 = BitmapFactory.decodeResource(getResources(), R.drawable.pongpaddle);
paddle2 = BitmapFactory.decodeResource(getResources(), R.drawable.pongpaddle);
}
protected void onDraw(Canvas canvas) {
xp1 = canvas.getWidth()/2;
xp2 = canvas.getWidth()/2;
yp1 = 25;
yp2 = 760;
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(paddle1, xp1,yp1, null);
canvas.drawBitmap(paddle2,xp2,yp2, null);
Paint white = new Paint();
white.setColor(Color.WHITE);
canvas.drawText("Score P1:"+ p1Score +" P2: " + p2Score , 700, 20,white );
}
Based on your comment above, I think what's happening is, android run time is drawing to the canvas, and your onDraw is not being called. You can avoid this by calling this.setWillNotDraw(false) in your class' constructor. Once you clear this flag, your onDraw() will be called.
Source: Android developer docs says if you override View's onDraw(), you have to clear this flag. Check setWillNotDraw