I newbie to android and graphic development.
I implement a custom-view and want to draw to canvas.
I set the customview size to be (width x (width/2)) so the rectangle height is half of the width.
Now when start drawing, its easy for me to draw on a square area like 1X1.
like this:
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int ChosenSize = Math.min(widthSize, heightSize);
int width = ChosenSize;
int height = (int)(ChosenSize/2);
setMeasuredDimension(width,height);
}
#Override protected void onDraw(Canvas canvas){
float width = (float)getWidth();
float height = (float)getHeight();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(width ,height );
// start drawing in a 1X1 square coordinates
canvas.restore();
}
But after restore() the result is stretched a little bit.
What am i doing wrong ??
Note: I have notice that when not using scale and working with the rectangle width and height the
result looks Ok
Thx
I figure it out.
The reason my drawing stretch, is because I scale from rectangle to square which is mistake.
Usually when canvas is square it easy to draw on 1x1 square, so I scale to canvas.scale(width,height),
But when canvas is rectangle like (width,width/2), I should scale to (width,height/width) to get rect of 1x2.
so after I change scaling to canvas.scale(width,height/width)
Related
I am writing a custom view in android. I want to draw a circle that cover all width and height of my view. this is my code
private void init() {
bgpaint = new Paint();
bgpaint.setColor(bgColor);
bgpaint.setAntiAlias(true);
bgpaint.setStyle(Paint.Style.STROKE);
bgpaint.setStrokeWidth(strokeWidth);
rect = new RectF();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw background circle anyway
int strokeWidth = 50;
rect.set(strokeWidth, strokeWidth, getwidth()- strokeWidth,
getheight() - strokeWidth);
canvas.drawArc(rect, -90, 360, fill, bgpaint);
}
But when I run result will be like this
I want be like this
What the problem with my code?
Probem is in setting your rectangle's coordinates. You must set half of stroke width, not whole stroke width.
float left=strokeWidth / 2;
float top=strokeWidth / 2;
float right=minDimen - strokeWidth/ 2;
float bottom=minDimen - strokeWidth / 2;
rect.set(left, top, right,bottom);
Because it comes to circle, I assume your width and height of your circleView are the same dimension. That is minDimen in my code. Because I use smaller dimension of those two.
final int minDimen = Math.min(width, height);
setMeasuredDimension(minDimen, minDimen);
(this must be called in onMeasure method)
Anyway, it's good method to set width and height in same dimensions.
I try to implement my own drawable that will corss fade from a simple colored rectangle (that should have the same size as the ImageView where the drawable is in) to a loaded bitmap (over http).
So what I do is override the draw() method of my Drawable:
public void draw(Canvas canvas) {
boolean done = true;
Log.d("Test",
"canvas w: " + canvas.getWidth() + " " + canvas.getHeight());
final int alpha = mAlpha;
final boolean crossFade = mCrossFade;
Paint paint = mStartPaint;
paint.setColor(blueColor);
if (!crossFade || 255 - alpha > 0) {
if (crossFade) {
paint.setAlpha(255 - alpha);
}
// Draw the rect with the color
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
if (crossFade) {
paint.setAlpha(0xFF);
}
}
if (alpha > 0) {
Bitmap bitmap = mEnd;
paint = mEndPaint;
paint.setAlpha(alpha);
if (mBitmapScalingType == null)
canvas.drawBitmap(bitmap, mEndX, mEndY, paint);
else
mBitmapScalingType.draw(bitmap, canvas.getWidth(),
canvas.getHeight(), canvas, paint);
paint.setAlpha(0xFF);
}
if (!done) {
mHandler.post(mInvalidater);
}
}
So the canvas.getWidth() and canvas.getHeight() returns 128 (width) and 150 (height) which is the same as the ImageView has. But the result is:
The blue rectangle has not been painted over the complete canvas and I can't get figure out why.
The ImageView that displays my fadeable drawable has no specific scaletype set.
Any ideas what could be wrong?
I have also noticed that the width and height calculated from setBounds() is 180 x 212 px
#Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
final int width = right - left;
final int height = bottom - top;
Log.d("Test", "width: "+width+" height: "+height);
}
and if I draw the color with 180 x 212 px
canvas.drawRect(0, 0, 180, 212, paint);
the rectangular is now drawn to fill the canvas completely.
Any idea what could be wrong?
Btw. The ImageView is defined like that:
<ImageView android:id="#+id/playerPic"
android:layout_width="64dp"
android:layout_height="75dp"
android:layout_marginRight="10dp"
/>
in xhdpi 64dp = 128px and 75dp = 150 px
This answer doesn't explain the difference in dimensions, but sometimes it's easier to just do things a different way.
If you just want to fill the canvas with a color, you don't need to use drawRect(), just use drawColor(). There's the simple version that just takes a Color, and one that lets you specify a porter-duff mode. This way you don't need dimensions at all, and you can avoid the whole thing.
Maybe you can try to use DisplayMetrics to get the dimensions.
//Get the width and height of the screen
DisplayMetrics d = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(d);
int width = d.widthPixels;
int height = d.heightPixels;
Then just call drawrect with those values
canvas.drawRect(0, 0, width, height, paint);
I have asked questions related to this before, but I think I was framing my objective incorrectly.
What I have: a custom ImageView that displays a graphic and defines multiple touchable areas as rectangles within the image. My problem is scaling. I want to define the touchable area of the image based on it's actual resolution in the bitmap file, but translate those coordinates so the the rectangle covers the same area on the scaled image.
This is what I've got so far:
When the view is created, calculate the ratio of the actual to scaled sizes
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Drawable pic=this.getDrawable();
int realHeight= pic.getIntrinsicHeight();
int realWidth=pic.getIntrinsicWidth();
int scaledHeight=this.getHeight();
int scaleWidth=this.getWidth();
heightRatio=(float)realHeight/scaledHeight;
widthRatio=(float)realWidth/scaleWidth;
}
Now I want to take the coordinates that define rectangle(s) on the original (un-scaled) image
and draw that rectangle(s) to the same area of the image -- but accounting for scale:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p=new Paint();
p.setStrokeWidth(1);
p.setColor(Color.BLUE);
for (HotSpot h: spots)
{
//Each hotspot has a rectangle defined in reference to the actual size of the image
Rect old=h.getRect();
float offsetLeft=old.left+(old.left*widthRatio);
float offsetTop=old.top+(old.top*heightRatio);
float offsetRight=old.right+(old.right*heightRatio);
float offsetBottom=old.bottom+(old.bottom*widthRatio);
RectF nRect=new RectF(offsetLeft,offsetTop,offsetRight,offsetBottom);
canvas.drawRect(nRect,p);
}
The results are "in the ball park" but not quite accurate. Any help is appreciated.
You can try this solution:
Get the screen density
Get the image height (or width)
Divide the height (or width) by the density, so you get the length in inches
Divide the coordinate by the length in inches, so you get a relationship between the coordinate and the image which is independent by the image effective size
When you have to draw the same point on a differently scaled image, multiply the last result for the length in inches of the second image (which you obtain using the same operations listed above)
Example:
On the first image:
float density = getResources().getDisplayMetrics().density;
int width = getWidth();
float inchesLength = width/density;
float scaledXCenter = xCenter / inchesLength;
On the same image with a different scale:
float density = getResources().getDisplayMetrics().density;
int width = getWidth();
float inchesLength = width/density;
float restoredXCenter = scaledXCenter * inchesLength;
I am trying to create a tab-like interface where I will switch views by sliding left/right. I display to the user what page they are on by adding a blue glow to the back of the tab icon shown here:
I did this by creating a custom view that will draw the background glow (if the tab is selected) and then the icon. My issue is that the glow is too large. All I need to do is to make it smaller so the sides don't get clipped like they are now (the glow overlaps the page on the bottom by design).
The glow was created using a shape drawable in XML (just a simple radial gradient).
I have tried changing the size of the shape in XML but it always comes out the same. I tried adjusting attributes under and tried adjusting the height and width under too.
Constructor:
public TabIconView(Context context, Bitmap tab)
{
super(context);
back = BitmapFactory.decodeResource(getResources(), R.drawable.background_blue_glow);
this.tab = tab;
}
onMeasure and onDraw:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(tab.getWidth() * 2, tab.getHeight() * 2);
}
#Override
protected void onDraw(Canvas canvas)
{
width = getMeasuredWidth();
height = getMeasuredHeight();
if (selected)
canvas.drawBitmap(back, (width - back.getWidth()) / 2, (height - back.getHeight()) / 2, null);
canvas.drawBitmap(tab, (width - tab.getWidth()) / 2, (height - tab.getHeight()) / 2, null);
super.onDraw(canvas);
}
Any ideas will be greatly appreciated.
One of the versions of Canvas.drawBitmap() lets you specified the width and height that you want the bitmap to have when drawn on screen. You could also simply rescale the bitmap when you load it (the Bitmap class has methods to create scaled versions of bitmaps.)
Figured it out. In case anybody wants to know...
public TabIconView(Context context, Bitmap tab)
{
super(context);
int backSize = (int) (42 * context.getResources().getDisplayMetrics().density + .5f);
b = BitmapFactory.decodeResource(getResources(), R.drawable.background_blue_glow);
back = Bitmap.createScaledBitmap(b, backSize, backSize, false);
this.tab = tab;
}
I had to create a scaled bitmap from the bitmap that was generated from the shapedrawable.
I have a Bitmap that is larger than the ImageView that I'm putting it in. I have the ScaleType set to center_inside. How do I get the dimensions of the scaled down image?
Ok. I probably should have been clearer. I needed the height and width of the scaled Bitmap before it's ever drawn to the screen so that I could draw some overlays in the correct position. I knew the position of the overlays in the original Bitmap but not the scaled. I figured out some simple formulas to calculate where they should go on the scaled Bitmap.
I'll explain what I did in case someone else may one day need this.
I got the original width and height of the Bitmap. The ImageView's height and width are hard-coded in the xml file at 335.
int bitmap_width = bmp.getWidth();
int bitmap_height = bmp.getHeight();
I determined which one was larger so that I could correctly figure out which one to base the calculations off of. For my current example, width is larger. Since the width was scaled down to the the width of the ImageView, I have to find the scaled down height. I just multiplied the ratio of the ImageView's width to the Bitmap's width times the Bitmap's height. Division is done last because Integer division first would have resulted in an answer of 0.
int scaled_height = image_view_width * bitmap_height / bitmap_width;
With the scaled height I can determine the amount of blank space on either side of the scaled Bitmap by using:
int blank_space_buffer = (image_view_height - scaled_height) / 2;
To determine the x and y coordinates of where the overlay should go on the scaled Bitmap I have to use the original coordinates and these calculated numbers. The x coordinate in this example is easy. Since the scaled width is the width of the ImageView, I just have to multiply the ratio of the ImageView's width to the Bitmap's width with the original x coordinate.
int new_x_coord = image_view_width * start_x / bitmap_width;
The y coordinate is a bit trickier. Take the ratio of the Bitmap's scaled height to the Bitmap's original height. Multiply that value with the original y coordinate. Then add the blank area buffer.
int new_y_coord = scaled_height * start_y / bitmap_height + blank_space_buffer;
This works for what I need. If the height is greater than the width, just reverse the width and height variables.
Here's how I do it:
ImageView iv = (ImageView)findViewById(R.id.imageview);
Rect bounds = iv.getDrawable().getBounds();
int scaledHeight = bounds.height();
int scaledWidth = bounds.width();
You can use Drawable's getIntrinsicWidth() or height method if you want to get the original size.
EDIT: Okay, if you're trying to access these bounds at the time onCreate runs, you'll have to wait and retrieve them till after the layout pass. While I don't know that this is the best way, this is how I've been able to accomplish it. Basically, add a listener to the ImageView, and get your dimensions just before it's drawn to the screen. If you need to make any layout changes from the dimensions you retrieve, you should do it within onPreDraw().
ImageView iv = (ImageView)findViewById(R.id.imageview);
int scaledHeight, scaledWidth;
iv.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
Rect rect = iv.getDrawable().getBounds();
scaledHeight = rect.height();
scaledWidth = rect.width();
iv.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
Try this code:
final ImageView iv = (ImageView) findViewById(R.id.myImageView);
ViewTreeObserver vto = iv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Toast.makeText(MyActivity.this, iv.getWidth() + " x " + iv.getHeight(), Toast.LENGTH_LONG).show();
iv.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
Otherwise you can use onSizeChange() by making this view a custom one.