Canvas Path using offset make to many copies (onDraw running twice) - android

seems I have a problem to understand how offset works. When I use the code below it create me 5 arrowheads, but why? I create one and the use twice offset. So i thought i get 3 arrow heads.
public class legende extends View {
private Paint paint;
private Path arrowPath;
public legende(Context context) {
super(context);
init();
}
public void init(){
paint = new Paint();
arrowPath = new Path();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 1500;
int height = 5000;
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
arrowPath.moveTo(420, 300);
arrowPath.lineTo(430, 300);
arrowPath.lineTo(420, 310);
arrowPath.lineTo(410, 300);
arrowPath.close();
canvas.drawPath(arrowPath, paint);
arrowPath.offset(0,200);
canvas.drawPath(arrowPath, paint);
arrowPath.offset(0,380);
canvas.drawPath(arrowPath, paint);
}
}
EDIT
Looks like, the problem is, that onDraw is called twice, but i don't know why.
This is how I use it in my fragment activity:
hScrollView.addView(new legende(getActivity()));
scrollView.addView(hScrollView);
relativeLayout.addView(scrollView);

Looks like you´ve got a non thread safe implementation of the onDraw method.
This guy worked it out by using a thread safe implementation: Custom View not drawing properly
Maybe you should copy the path to a local object inside the onDraw and then alter and paint.

Related

Canvas DrawLine Is Invisible

I am trying to draw a single line in Android using canvas
My class :
public class LineDrawer extends View {
public LineDrawer(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(10);
float left = 20;
float top = 20;
float right = 50;
float bottom = 100;
canvas.drawLine(left, top, right, bottom, paint);
}
}
My Main Activity :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LineDrawer lineDrawer = new LineDrawer(this);
setContentView(R.layout.activity_Main);
}
}
I cannot find where is the problem , I try all the solutions in the internet but nothing happen , still a blank activity..
Should I import some code ?
lineDrawer is created but not added anywhere. Just creating a view is not enough, you need to add it to the current displayed views to be taken into account and drawn. You have two options:
Add it to your XML layout. You will have to add the following constructor to your custom view.
public LineDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
Use addView(). Anyway, given how simple is your example, I'll use first (common) method.
As an additional comment, the Paint paint object should be created on view initialization, as is a costly operation. See in the original documentation for more information about this.

PorterDuff Multiply on a path over an ImageView turns transparency into black

I would like to draw a path over an image, and fill that path with a color that uses a PorterDuff multiply xfermode. However, it appears that Android completes the portions of the rectangle containing the path that should be transparent with black. This only happens when hardware acceleration is turned on on the activity - when it's off, it works as intended.
What I want (and what happens with hardware acceleration is off)
What I get
My (simplified) code so far:
public class ImageOverlayTest extends ImageView {
private Paint mOverlayPaint;
private Path mOverlayPath;
public ImageOverlayTest(Context context, AttributeSet attrs){
super(context, attrs);
init();
}
private void init() {
mOverlayPaint = new Paint();
mOverlayPaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
mOverlayPaint.setColor(Color.RED);
}
protected void createOverlayPath() {
mOverlayPath = new Path();
mOverlayPath.moveTo(0, 0);
mOverlayPath.lineTo(0, 400);
mOverlayPath.lineTo(getWidth() / 3, 600);
mOverlayPath.lineTo(getWidth() * 2 / 3, 600);
mOverlayPath.lineTo(getWidth(), 400);
mOverlayPath.lineTo(getWidth(), 0);
mOverlayPath.lineTo(0, 0);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
createOverlayPath();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mOverlayPath, mOverlayPaint);
}
}
Having hardware acceleration off really kills the performance of other aspects of my activities (simple animations), so I need to avoid that. Likewise, an easier, but uglier, solution is to just overlay the color with some transparency, but that's not the effect I'd like.
Thanks for any insight you may have.
Figured it out myself, you can disable hardware acceleration on the View itself by calling
setLayerType(LAYER_TYPE_SOFTWARE, null);

Drawing over a view and all it's children

I'm trying to apply a visual effect to a viewgroup. My idea is to grab a bitmap of the viewgroup, shrink it down, expand it back up, and draw it over the viewgroup to give it a blocky, low quality effect.
I've got most of the way there using this code:
public class Blocker {
private static final float RESAMPLE_QUALITY = 0.66f; // less than 1, lower = worse quality
public static void block(Canvas canvas, Bitmap bitmap_old) {
block(canvas, bitmap_old, RESAMPLE_QUALITY);
}
public static void block(Canvas canvas, Bitmap bitmap_old, float quality) {
Bitmap bitmap_new = Bitmap.createScaledBitmap(bitmap_old, Math.round(bitmap_old.getWidth() * RESAMPLE_QUALITY), Math.round(bitmap_old.getHeight() * RESAMPLE_QUALITY), true);
Rect from = new Rect(0, 0, bitmap_new.getWidth(), bitmap_new.getHeight());
RectF to = new RectF(0, 0, bitmap_old.getWidth(), bitmap_old.getHeight());
canvas.drawBitmap(bitmap_new, from, to, null);
}
}
I simply pass in the canvas to draw on and a bitmap of what needs to be scaled down+up and it works well.
public class BlockedLinearLayout extends LinearLayout {
private static final String TAG = BlockedLinearLayout.class.getSimpleName();
public BlockedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
applyCustomAttributes(context, attrs);
setup();
}
public BlockedLinearLayout(Context context) {
super(context);
setup();
}
private void setup() {
this.setDrawingCacheEnabled(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// block(canvas); If I call this here, it works but no updates
}
#Override
public void onDraw(Canvas canvas) {
// block(canvas); If I call this here, draws behind children, still no updates
}
private void block(Canvas canvas) {
Blocker.block(canvas, this.getDrawingCache());
}
}
The problem I'm having is in my viewgroup. If I run the block method in the viewgroup's draw, it draws over everything but doesn't ever update when child views change. I've traced function calls with Log, and the draw method seems to be running, but nothing changes.
I've also tried implementing this in onDraw. This draws the bitmap behind all the children views, and again they aren't updating.
Can anyone explain how I would go about fixing this?
Try this:
#Override
protected void dispatchDraw(Canvas canvas) {
// call block() here if you want to draw behind children
super.dispatchDraw(canvas);
// call block() here if you want to draw over children
}
And call destroyDrawingCache() and then, buildDrawingCache() each time you change a child.
Draw() method will work well for you.
I'm now trying to make a count time view in a circle shape, when time is passing, the view will reducing his angle. It's used to cover profile photo(a circle shape photo).
Starting with Android API 23, you can use onDrawForeground(Canvas) to draw on top of child views: https://developer.android.com/reference/android/view/View#onDrawForeground(android.graphics.Canvas)
Unlike onDraw() though, be sure to call through to the super class:
#Override
public void onDrawForeground(final Canvas canvas) {
super.onDrawForeground(canvas);
// Your code here
}

Android graphics outside of onDraw method

Okay, so I'm trying to draw to a canvas on Android from outside of the onDraw method.
It's just easiest to show my code:
public class TestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Paint p = new Paint();
p.setColor(Color.GREEN);
Panel a = new Panel(this,150,150,50,p);
a.drawThing();
setContentView(a);
}
class Panel extends View{
private float radius, x, y;
private Canvas CAN;
private Paint p;
public Panel(Context context, float x, float y, float radius, Paint p){
super(context);
this.x = x;
this.y = y;
this.radius = radius;
this.p = p;
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
CAN = canvas;
}
public void drawThing(){
CAN.drawCircle(x, y, radius, p);
}
}
}
Do you see what I'm trying to do? But for some reason it throws a NullPointerException
Many of the graphics resources are explicitly freed/released after they've been used. I'm not exactly sure why they do this, but whatever the reason, they don't you to do what you're trying.
Instead of drawing outside of the onDraw method, use some kind of flag to change what the onDraw method is doing. When you want to draw some specific thing, you can set the right flag, and call invalidate().
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
if (doThing) {
canvas.drawCircle(x, y, radius, p);
}
}
EDIT
Something else to consider is drawing to and "off-scrren" source. This means using some kind of graphics representation like a bitmap as a buffer that you can draw to in other code. This won't update your gui, but it will give you the chance to do some heavy duty drawing without locking up the user's device. Once you are done drawing to the bitmap (or whatever) you can invalidate your view and draw it to the screen in the onDraw(Canvas) method.
I'm pretty sure that the null pointer happens because you're calling drawSomething before onDraw ever gets called. So CAN is null.
You can draw onto canvas outside of the onDraw. See this Can we have two canvases in an activity ? (OR) Having a canvas outside the onDraw() is not working for more info.

Scale image within custom ImageView

I'm extending ImageView in order to manually scale an image within the view. I want to scale an image to fill the width of the custom view, and then draw it to the canvas, however, I'm unable to get the view width using this.getWidth()
It just returns 0, as the view has not yet been drawn and so has dimensions 0 by 0.
Currently I have the following in my main.xml:
<com.android.myapp.BackgroundView
android:id="#+id/background_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:src="#drawable/background"
android:dither="true"
/>
The custom class is as follows:
public class BackgroundView extends ImageView {
private Paint paint;
private Bitmap background;
public BackgroundView(Context context) {
super(context);
paint = new Paint();
loadBitmap();
}
public BackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
loadBitmap();
}
public void loadBitmap() {
BitmapDrawable src = (BitmapDrawable) this.getDrawable();
background = src.getBitmap();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(background, 0, 0, paint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
My Main.java class is:
public class Main extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
I can't use
Bitmap.createScaledBitmap(background, view.getWidth(), view.getHeight(), false)
as the view hasn't yet been drawn, how would I go about scaling the image to fill the view/screen width at this point?
Thanks in advance.
I believe what you want to do could also be achieved by using ImageView directly in conjunction with the scaleType attribute. Either use fitXY or centerCrop, depending on your needs.
But to answer the question, you can only use getWidth() and getHeight() after layout() has been called. So you should be able to use the values inside your onDraw method.
Also you could use another drawBitmap method so you wouldn't have to create a new bitmap in memory.

Categories

Resources