Currently I'm making some changes in app for android. It currently works nicely in portrait mode, but not in landscape mode. The thing is that when in landscape mode, text on my canvas exceeds height of screen (therefore, it's not readable). I need to add scrolls on canvas if canvas size exceeds screen size. (I've already made changes to .xml to make layout scrollView but it doesn't help since canvas is set to be the size of the screen, and not the size of it's content)
Ill paste the entire class, but the important part is onDraw. This class is used from main activity, and then main activity. Question is pretty straight forward. Is there a way to dynamically add scrolls if canvas is bigger than screens? Or should I use another activity for writing results and canvas only for drawing? If need be, here's link to entire project and there is zip with all the code (app is really not that big):
http://www.yorku.ca/mack/FittsLawSoftware/
package ca.yorku.cse.mack.fittstouch;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Toast;
public class ExperimentPanel extends View
{
final int START_TEXT_SIZE = 20; // may need to fiddle with this, depending on device
final int START_CICLE_DIAMETER = 53; // x pixelDensity = one-third inch
final int GAP_BETWEEN_LINES = 6;
Target[] targetSet;
Target toTarget; // the target to select
Target fromTarget; // the source target from where the trial began
Target startCircle;
float panelWidth;
float panelHeight;
float d; // diameter of start circle (also used for positioning circle and text)
float textSize;
float gap;
boolean waitStartCircleSelect, done;
String mode;
Paint targetPaint, targetRimPaint, normalPaint, startPaint;
String[] resultsString = {"Tap to begin"};
public ExperimentPanel(Context contextArg)
{
super(contextArg);
initialize(contextArg);
}
public ExperimentPanel(Context contextArg, AttributeSet attrs)
{
super(contextArg, attrs);
initialize(contextArg);
}
public ExperimentPanel(Context contextArg, AttributeSet attrs, int defStyle)
{
super(contextArg, attrs, defStyle);
initialize(contextArg);
}
// things that can be initialized from within this View
private void initialize(Context c)
{
this.setBackgroundColor(Color.LTGRAY);
float pixelDensity = c.getResources().getDisplayMetrics().density;
d = START_CICLE_DIAMETER * pixelDensity;
startCircle = new Target(Target.CIRCLE, d, d, d, d, Target.NORMAL);
textSize = START_TEXT_SIZE * pixelDensity;
gap = GAP_BETWEEN_LINES * pixelDensity;
targetPaint = new Paint();
targetPaint.setColor(0xffffaaaa);
targetPaint.setStyle(Paint.Style.FILL);
targetPaint.setAntiAlias(true);
targetRimPaint = new Paint();
targetRimPaint.setColor(Color.RED);
targetRimPaint.setStyle(Paint.Style.STROKE);
targetRimPaint.setStrokeWidth(2);
targetRimPaint.setAntiAlias(true);
normalPaint = new Paint();
normalPaint.setColor(0xffff9999); // lighter red (to minimize distraction)
normalPaint.setStyle(Paint.Style.STROKE);
normalPaint.setStrokeWidth(2);
normalPaint.setAntiAlias(true);
startPaint = new Paint();
startPaint.setColor(0xff0000ff);
startPaint.setStyle(Paint.Style.FILL);
startPaint.setAntiAlias(true);
startPaint.setTextSize(textSize);
}
#Override
protected void onDraw(Canvas canvas)
{
int tmp1=0;
float tmp2 =0;
if (waitStartCircleSelect) // draw start circle and prompt/results string
{
canvas.drawCircle(startCircle.xCenter, startCircle.yCenter, startCircle.width / 2f,
startPaint);
for (int i = 0; i < resultsString.length; ++i){
canvas.drawText(resultsString[i], d / 2, d / 2 + 2 * startCircle.width / 2f + (i + 1) * (textSize + gap), startPaint);
tmp1=i;
}
} else if (!done) // draw task targets
{
for (Target value : targetSet)
{
if (mode.equals("1D"))
canvas.drawRect(value.r, normalPaint);
else // 2D
canvas.drawOval(value.r, normalPaint);
}
// draw target to select last (so it is on top of any overlapping targets)
if (mode.equals("1D"))
{
canvas.drawRect(toTarget.r, targetPaint);
canvas.drawRect(toTarget.r, targetRimPaint);
} else // 2D
{
canvas.drawOval(toTarget.r, targetPaint);
canvas.drawOval(toTarget.r, targetRimPaint);
}
}
invalidate(); // will cause onDraw to run again immediately
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension((int) panelWidth, (int) panelHeight);
}
}
Related
I want to create a heart shape drown on screen when user touches the smartphone screen(working on a drawing android app). So i setup the BitmapShader as below code
//Initialize the bitmap object by loading an image from the resources folder
Bitmap fillBMP = BitmapFactory.decodeResource(context.getResources(), R.drawable.heart);
fillBMP = Bitmap.createScaledBitmap(fillBMP, 20, 20, false);
//Initialize the BitmapShader with the Bitmap object and set the texture tile mode
shader= new BitmapShader(fillBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Then i assign the shader paint object.
paint.setShader(preset.shader);
I have also setup the touch listener to track the user finger. On user touch i draw this on canvas object as.
Path path = new Path();
path.moveTo(mid1.x, mid1.y);
path.quadTo(midmid.x, midmid.y, mid2.x, mid2.y);
canvas.drawPath(path, paint);
This give me this
Where some heart shape are croped and also these are repeated. What i want not these are repeated and never croped like this.
Thanks in advance.
I had a bit of trouble uploading to Git, so for now, I'll post the solution here.
Here's a helper class I wrote to create the effect you want. I added comments in-line to explain what I'm doing.
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
// Use this class with a Canvas to create the effect you want.
public class CurvedBitmapDrawer
{
private Context mContext;
private Paint mPaint;
private int mResourceId;
private Bitmap mBitmap;
private Path mPath;
private int mBitmapMargin;
// Create with a context so that this class can use resource ids.
public CurvedBitmapDrawer(Context context) {
mContext = context;
mPath = new Path();
}
// getters setters for paint.
// this paint will be used to draw the bitmaps, and the strokeWidth value in the paint
// will be used to set the thickness of the curve / line that is drawn.
public Paint getPaint() {
return mPaint;
}
public void setPaint(Paint paint) {
mPaint = paint;
}
// getters setters for the space between the bitmaps.
public int getBitmapMargin()
{
return mBitmapMargin;
}
public void setBitmapMargin(int bitmapMargin)
{
mBitmapMargin = bitmapMargin;
}
// getters setters for res id
public int getResourceId() {
return mResourceId;
}
public void setResourceId(int resourceId)
{
mResourceId = resourceId;
mBitmap = null;
}
// alternative optional getters setters for bitmap.
public Bitmap getBitmap()
{
return mBitmap;
}
public void setBitmap(Bitmap bitmap)
{
mBitmap = bitmap;
mResourceId = 0;
}
// I decided to only use a local path here, but feel free to change it.
// call getPath to perform actions on the path that is drawn by this class.
public Path getPath()
{
return mPath;
}
// draw method. comments inline.
public void draw(Canvas canvas)
{
// grab a bitmap in the desired size.
final Bitmap scaledBitmap = getScaledBitmap();
// find the center of the bitmap.
final float centerX = scaledBitmap.getWidth() / 2;
final float centerY = scaledBitmap.getHeight() / 2;
// wrap the path with a measurement tool for paths - PathMeasure
final PathMeasure pathMeasure = new PathMeasure(mPath, false);
// initialize the distance to the center of the bitmap.
float distance = scaledBitmap.getWidth() / 2;
// initialize position and slope buffers.
float[] position = new float[2];
float[] slope = new float[2];
float slopeDegree;
// draw so long as the distance traveled on the path isn't longer than
// the total distance of the path.
while (distance < pathMeasure.getLength())
{
// grab the position & slope (tangent) on a particular distance along the path.
pathMeasure.getPosTan(distance, position, slope);
// convert the vector to a degree.
slopeDegree = (float)((Math.atan2(slope[1], slope[0]) * 180f) / Math.PI);
// preserve the current state of the canvas
canvas.save();
// translate the canvas to the position on the path.
canvas.translate(position[0] - centerX, position[1] - centerY);
// rotate the canvas around the center of the bitmap the amount of degrees needed.
canvas.rotate(slopeDegree, centerX, centerY);
// draw the bitmap
canvas.drawBitmap(scaledBitmap, 0, 0, mPaint);
// revert the bitmap to the previous state
canvas.restore();
// increase the distance by the bitmap's width + the desired margin.
distance += scaledBitmap.getWidth() + mBitmapMargin;
}
}
// returns a scaled bitmap from the asset specified.
private Bitmap getScaledBitmap()
{
// no bitmap or resId, return null (no special handing of this! add if you like).
if (mBitmap == null && mResourceId == 0)
return null;
// if no bitmap is specified, create one from the resource id.
// Optimization: be sure to clear the bitmap once done.
if (mBitmap == null)
mBitmap = BitmapFactory.decodeResource(mContext.getResources(), mResourceId);
// width / height of the bitmap[
float width = mBitmap.getWidth();
float height = mBitmap.getHeight();
// ratio of the bitmap
float ratio = width / height;
// set the height of the bitmap to the width of the path (from the paint object).
float scaledHeight = mPaint.getStrokeWidth();
// to maintain aspect ratio of the bitmap, use the height * ratio for the width.
float scaledWidth = scaledHeight * ratio;
// return the generated bitmap, scaled to the correct size.
return Bitmap.createScaledBitmap(mBitmap, (int)scaledWidth, (int)scaledHeight, true);
}
}
Here's a usage example:
ImageView image = (ImageView)findViewById(R.id.img);
CurvedBitmapDrawer drawer = new CurvedBitmapDrawer(this);
Paint paint = new Paint();
paint.setStrokeWidth(50);
drawer.setPaint(paint);
drawer.setResourceId(R.drawable.heart_icon);
drawer.setBitmapMargin(10);
Path path = drawer.getPath();
path.moveTo(80, 90);
path.cubicTo(160, 470, 750, 290, 440, 880);
Bitmap finalBitmap = Bitmap.createBitmap(800, 1000, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(finalBitmap);
drawer.draw(canvas);
image.setImageBitmap(finalBitmap);
I've tested it here and it seems to work well, minus some edge cases perhaps. Here's what it looks like with the given usage example:
Remember to set the stroke width on the paint object you supply, or else nothing will be drawn (and probably will cause an exception with the current code).
Hope this helps you.
Ok, I need to crop an ImageView in a particular shape, and I can't do this by adding over a png, because the background can be variable (ex. a pattern). So, I need that the area outside the shape is transparent.
The shape must be this:
I thought to use Path() to draw this shape and use it to mask the ImageView, but I have absolutely no idea how to draw a complex shape like this with Path().
Many thanks.
So I was bored and this looked like fun, so I've thrown together a simple Drawable you can use to do this. You could get fancier and add strokes and whatnot to it, but this works for the basic case you've suggested, and allows you to set the arrow to be pointing to any of the corners, and will also scale your image to fit the bounds of the Drawable. Here's the result:
You can use it by:
BubbleDrawable bubbleDrawable = new BubbleDrawable(
this, R.drawable.your_image, BubbleDrawable.Corner.TOP_RIGHT);
myImageView.setImageDrawable(bubbleDrawable);
And here's the code for BubbleDrawable:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import static android.graphics.Matrix.ScaleToFit.FILL;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Path.Direction.CW;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.graphics.Shader.TileMode.CLAMP;
import static test.com.testrotationanimation.BubbleDrawable.Corner.TOP_LEFT;
public final class BubbleDrawable extends Drawable {
private final Matrix mMatrix = new Matrix();
private final Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
private final RectF mSrcRect = new RectF();
private final RectF mDstRect = new RectF();
private final Shader mShader;
private Corner mArrowCorner = TOP_LEFT;
public BubbleDrawable(Bitmap bitmap, Corner arrowCorner) {
// Initialize a BitmapShader with the image you wish to draw
// (you can use other TileModes like REPEAT or MIRROR if you prefer)
mShader = new BitmapShader(bitmap, CLAMP, CLAMP);
mPaint.setShader(mShader);
// Save the bounds of the bitmap as the src rectangle -- will
// be used later to update the matrix when the bounds change
// so that the image fits within the bounds of this drawable
mSrcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
// Set the corner in which the arrow will be drawn
mArrowCorner = arrowCorner;
}
public BubbleDrawable(Context ctx, int drawableResource, Corner arrowCorner) {
this(BitmapFactory.decodeResource(ctx.getResources(), drawableResource), arrowCorner);
}
public Corner getArrowCorner() {
return mArrowCorner;
}
public void setArrowCorner(Corner corner) {
mArrowCorner = corner;
updatePath();
invalidateSelf();
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateMatrix(bounds);
updatePath();
}
private void updateMatrix(Rect bounds) {
// Set the destination rectangle for the bitmap to be the
// new drawable bounds
mDstRect.set(bounds);
// Scale the bitmap's rectangle to the bounds of this drawable
mMatrix.setRectToRect(mSrcRect, mDstRect, FILL);
// Update the shader's matrix (to draw the bitmap at the right size)
mShader.setLocalMatrix(mMatrix);
}
private void updatePath() {
final Rect bounds = getBounds();
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
// Draw the initial circle (same for all corners)
mPath.reset();
mPath.addCircle(x, y, Math.min(x, y), CW);
// Add the rectangle which intersects with the center,
// based on the corner in which the arrow should draw
switch (mArrowCorner) {
case TOP_LEFT:
mPath.addRect(bounds.left, bounds.top, x, y, CW);
break;
case TOP_RIGHT:
mPath.addRect(x, bounds.top, bounds.right, y, CW);
break;
case BOTTOM_LEFT:
mPath.addRect(bounds.left, y, x, bounds.bottom, CW);
break;
case BOTTOM_RIGHT:
mPath.addRect(x, y, bounds.right, bounds.bottom, CW);
break;
}
}
#Override
public void draw(Canvas canvas) {
// Easy enough, just draw the path using the paint.
// It already has the BitmapShader applied which
// will do the work for you.
canvas.drawPath(mPath, mPaint);
}
#Override
public int getOpacity() {
// Indicate that this Drawable has fully-transparent pixel values
return TRANSPARENT;
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
// Yay, you can even support color filters for your drawable
mPaint.setColorFilter(colorFilter);
}
#Override
public void setAlpha(int i) {
// You could do this by doing some canvas magic but I'm
// lazy and don't feel like it. Exercise for the reader. :)
throw new UnsupportedOperationException("Not implemented.");
}
public enum Corner {
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
}
}
The code will run through an array. If the character 'X' is seen in the array, it will drawRect at the respective spot on the screen.
The problem is that the color isn't actually being drawn on the screen. Some of the android websites talked about how you need a canvas, bitmap, etc. etc.
My question: What do I need to do to get the colours from the drawRect method to actually show up on the screen?
package com.example.routedrawingtest;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.graphics.Paint;
import android.view.View;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
float scale_x;
float scale_y;
public void main(String[] args){
#SuppressWarnings("unused")
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
int screen_width = size.x;
int screen_height = size.y;
scale_x = screen_width/125;
scale_y = screen_height/100;
}
public void onDraw(Canvas canvas) {
int i;
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
int j;
for (i=0; i<125; i++)
for (j=0; j<100; j++)
if (charMap[i][j]=='X')
canvas.drawRect(i*scale_x, j*scale_y, scale_x,scale_y, paint);
}
}
The method onDraw is not called by an Activity to do what you want, you need to extend the class View and implement the method there. Until you learn more when you add the view to a parent, you have to give it an explicit size like fill_parent or it will not show up on the screen.
For example:
public class Drawer extends View
{
Paint paint = new Paint();
public Drawer()
{
paint.setStyle(Style.STROKE);
paint.setStrokeColor(Color.BlUE);
// etc...
}
public void onDraw(Canvas canvas)
{
for (i=0; i<125; i++)
for (j=0; j<100; j++)
if (charMap[i][j]=='X')
canvas.drawRect(i*scale_x, j*scale_y, scale_x,scale_y, paint);
}
}
Add the view to your code via XML or via code like using it's fully qualified name:
XML:
<com.example.comp.Drawer
android:layout_width="fill_parent"<<<< specify a size
etc...
/>
I have started working on a game, and when the game ends it displays a board with the stats of the game. The winner's name, is drew on top of the board on an arch (using a class TextOnPath, which I will paste the code here). The text displays correctly in Android 4.1.2, but not the same thing happens to Android 4.0.3 or lower (I have attached images to show you this).
My questions is: what is wrong with my program? My min-SDK is 8 and I haven't used any new API introduces in JB. Please help me to find a solution.
This is my class(without the required methods for displaying the text):
public class TextOnPath extends View {
private String winner = "Computer";
private Path mArc;
int viewHeight, viewWidth;
private Paint mPaintText;
public TextOnPath(Context context) {
super(context);
}
public TextOnPath(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
int offset = 0;
if (offset < 16)
offset = (18 - winner.length()) * 11;
canvas.drawTextOnPath(winner, mArc, offset, 40, mPaintText);
invalidate();
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
viewWidth = xNew;
viewHeight = yNew;
Typeface chalk = Typeface.createFromAsset(getContext().getAssets(),
"fonts/Chalkduster.ttf");
// TODO Auto-generated constructor stub
mArc = new Path();
int pad = 20;
RectF oval = new RectF(pad, 0, viewWidth - pad, viewHeight);
mArc.addArc(oval, -180, 180);
mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintText.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintText.setColor(Color.WHITE);
int textsize = R.dimen.chalk_text_size;
System.err.println(textsize);
mPaintText.setTextSize(30f);
mPaintText.setTypeface(chalk);
this.setBackgroundResource(R.color.green);
}
Here I have posted 2 screenshots. 1'st one is from JB and the 2'nd one from ICS.
Thank you very much for your time!
drawTextOnPath() was not supported with hardware acceleration until Android 4.1. To work around this problem, simply set a software layer type on your View when running on Android < 4.1. Just call View.setLayerType(View.LAYER_TYPE_SOFTWARE, null). This will force software rendering and fix your problem.
Scrolling off screen bitmap, on SurfaceView, down one row does not work.
Try example code below to prove something seems broken in Android Java graphics bitmap system, or (more likely) there is something about this I do not understand, when scrolling an off screen bitmap down and drawing on on-screen SurfaceView canvas using drawBitmap.
Run code once and see how it smears the stroke only circle down the page like the circle was filled.
Then change the Y offset from 1 to - 1 and rerun the code and it scrolls up one line fine (hard to see as it's not animated).
package com.example.SurfaceViewTest;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class SurfaceViewTest extends Activity implements SurfaceHolder.Callback {
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private int intSurfaceWidth, intSurfaceHeight;
Bitmap bmpOffScreen;
Canvas cnvOffScreen = new Canvas();
Canvas c = new Canvas();
private static Paint paint = new Paint();
private static final String TAG = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSurfaceView = (SurfaceView) this.findViewById(R.id.Surface);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
paint.setColor(0xFFFFFFFF);
paint.setStyle(Style.STROKE);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
intSurfaceWidth = width;
intSurfaceHeight = height;
bmpOffScreen = Bitmap.createBitmap(intSurfaceWidth, intSurfaceHeight, Bitmap.Config.ARGB_8888);
cnvOffScreen = new Canvas(bmpOffScreen);
cnvOffScreen.drawCircle(intSurfaceWidth / 2, intSurfaceHeight / 2, 100, paint);
c = null;
c = mSurfaceHolder.lockCanvas(null);
if (c == null) {
Log.e(TAG, "Canvas bu-huu");
} else {
// Scroll off screen canvas and hopefully underlying bitmap bitmap
Rect src = new Rect(0, 0, intSurfaceWidth, intSurfaceHeight);
Rect dst = new Rect(0, 0, intSurfaceWidth, intSurfaceHeight);
dst.offset(0, 1);
cnvOffScreen.drawBitmap(bmpOffScreen, src, dst, null);
// This produces exact same smear, but works fine scrolling up!?!
cnvOffScreen.translate(0,1);
cnvOffScreen.drawBitmap(bmpOffScreen, 0, 0, paint);
c.drawBitmap(bmpOffScreen, 0, 0, paint);
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {}
}
There is a drawBitmap function within canvas with the following signature:
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint);
You can make your dst the size of the canvas, and your src can be set up to be new
Rect(0,offset,width,height+offset)
You can then increment offset every frame and get a smoothly scrolling bitmap in your view.
I had this problem and figured out why this happened. It's due to a bit of negligence in android's translate function. Basically it's because the function ONLY works by copying pixels left-to-right per row from top-to-bottom, without checking which direction you're translating to. So going left and down, it works because it is copying existing pixels to 'old' pixels, but the other way around, you're copying pixels to pixels you want to copy later on.
The trick to solving this is to use the faster translate function in the right/down translation, but use a slower, bitmap copying way for the other directions. Or maybe find some way to override the translate function with one which does it correctly, but I haven''t figured out how to do that yet.
I have a little writeup here:
http://spiralcode.wordpress.com/2013/06/17/long-time-no-see-of-rivers-cartography-and-tiling/