its my first time to ask a question here. I have this problem with my android app.
I would like to create different draw methods inside my custom view class. There are three buttons corresponding to 3 different shapes. When a button is pressed it will draw its shape. But the app crashes when I try to call the custom draw from the MainActivity for me to test it.
MainActivity
import com.example.shapes.view.ShapesView;
public class MainActivity extends Activity {
ShapesView shapesview;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
shapesview = (ShapesView) findViewById(R.id.ShapesViewID);
shapesview.DrawRectangle();
}
ShapesView
public class ShapesView extends View{
Canvas canvas;
public ShapesView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
this.canvas = canvas;
}
public void DrawRectangle() {
Paint mypaint = new Paint();
mypaint.setColor(Color.BLUE);
canvas.drawRect(30, 30, 200, 200, mypaint);
}
}
My XML layout file
<com.example.shapes.view.ShapesView
android:id="#+id/ShapesViewID"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content" />`
Please do help! Thank you very much!
If I understand your question you need a custom view (a button) which will draw itself differently based on some event. If that is the case then you need to respect the android's guidelines about the view drawing take a look here.
Now one possible solution for your case is to set some kind of flag about the state of your view and then use that flag when you are ready to draw. For example you can do this:
public class ShapesView extends View{
public int state = 0;
public ShapesView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (state == 1) {
Paint mypaint = new Paint();
mypaint.setColor(Color.BLUE);
canvas.drawRect(30, 30, 200, 200, mypaint);
}
}
and then whenever you need to draw your view from an activity you can use the following:
myview.state = 1;
myview.invalidate();
In your code what you are doing is that you call a views function during the onCreate of your activity which in turn tries to use a null canvas because the onDraw method of your view has not be called during that time. Furthermore as others have pointed out you must not use a canvas object outside the onDraw method.
Hope this helps...
I changed your code with following.
public class ShapesView extends View {
Paint mypaint;
public ShapesView(Context context, AttributeSet attrs) {
super(context, attrs);
mypaint = new Paint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mypaint.setColor(Color.BLUE);
canvas.drawRect(30, 30, 200, 200, mypaint);
}
}
When you want to draw, call shapesview.invalidate();
Look at method's name its onDraw() and not getCanvas(). The documentation also doesn't makes any claims about the Canvas provided.
After onDraw() is done, that canvas may be disposed, its bitmap/buffer may be re-cycled, who knows.
So, it is not safe to use the Canvas outside of this method. Draw what you want, but only inside onDraw() method.
If you want to trigger the View to re-draw, at some other time, call invalidate().
Example:
View class to render any shape:
public class ShapeView extends View {
private Paint mPaint;
private ShapeRenderer mRenderer;
public ShapeView(Context context) {
super(context);
init();
}
public ShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
mPaint = new Paint();
}
public void setPaintColor(int color){
mPaint.setColor(color);
}
public void setPaintStrokeWidth(float width){
mPaint.setStrokeWidth(width);
}
public void setRenderer(ShapeRenderer renderer) {
mRenderer = renderer;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mRenderer != null){
mRenderer.drawShape(canvas,mPaint);
}
}
public static interface ShapeRenderer{
public void drawShape(Canvas canvas, Paint paint);
}
}
A Class that draws a rectangle:
public class RectRenderer implements ShapeView.ShapeRenderer {
#Override
public void drawShape(Canvas canvas, Paint paint) {
canvas.drawRect(0,0,100,100,paint);
}
}
Draw a shape at runtime:
myShapeView.setPaintColor(Color.GREEN);
myShapeView.setPaintStroke(5f);
myShapeView.setRenderer(new RectRenderer());
myShapeView.invalidate();
I think you must redesign your component, so it can draw itself in onDraw method. If you need some class that must contain Canvas, you can try to do something like this.
class Drawer {
private Canvas canvas;
public Drawer(Canvas canvas) {
this.canvas = canvas;
}
public void DrawRectangle() {
Paint mypaint = new Paint();
mypaint.setColor(Color.BLUE);
canvas.drawRect(30, 30, 200, 200, mypaint);
}
}
On yours custom view you can do something like this.
public class ShapesView extends View{
public ShapesView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Drawer drawer = new Drawer(canvas);
drawer.DrawRectangle();
}
public void DrawRectangle() {
Paint mypaint = new Paint();
mypaint.setColor(Color.BLUE);
canvas.drawRect(30, 30, 200, 200, mypaint);
}
}
Related
I have 12 lines I've created using the following class
public class LineView extends View {
private Paint paint = new Paint();
private PointF pointA,pointB;
// private void init() {
// paint.setColor(Color.BLACK);
// }
public LineView(Context context) {
super(context);
// init();
}
public LineView(Context context, AttributeSet attrs) {
super(context, attrs);
// init();
}
public LineView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// init();
}
#SuppressLint("ResourceAsColor")
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
int color = R.color.GradientStart;
paint.setColor(color);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
//canvas.drawLine(x1, y1, x2, y2, paint);
canvas.drawLine(pointA.x, pointA.y, pointB.x, pointB.y, paint);
}
public void setPointA(PointF point){
pointA=point;
}
public void setPointB(PointF point){
pointB=point;
}
public void draw(){
invalidate();
requestLayout();
}
}
Instead of lines I what lines with arrows. The line with arrow will be drawn between buttons.
How can I add arrows to one end of my lines?
It would like like this when complete.
thanks
JN
I found two way for achieve your requirement.
1) To use nine patch image. I have tried to make nine patch image for you please use it
2) You can use vector image for it.
Tell me still you not getting solution.
I've searched for this question (the title) and I've got this:
A Rect created in a view class and passed into a fragment, but unfortunately it didn't work, I don't see the rectangle in fragment 1 when i run it.
what am i doing wrong, and Thank You in advance
public class Rectangle extends View {
public Rectangle(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas ) {
Rect rectangle = new Rect(200, 200, 200, 200);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
canvas.drawRect(rectangle, paint);
super.onDraw(canvas);
}
}
public class FragmentOne extends Fragment {
RelativeLayout relativeLayout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View myInflatedView= inflater.inflate(R.layout.fragment_one_layout,container,false);
relativeLayout = (RelativeLayout) myInflatedView.findViewById(R.id.Frag1);
relativeLayout.addView(new Rectangle(getActivity()));
return myInflatedView;
}
}
XML for fragment 1:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/Frag1"
android:layout_height="match_parent"
android:background="#000">
<com.redot.puzzle1.Rectangle
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
You problem is Rect rectangle = new Rect(200, 200, 200, 200);
The left-top coordinate is the same as the right-bottom coordinate .So it will not display in the layout.
Just change it in your code and try it .
Try this in your code .
public class Rectangle extends View {
public Rectangle(Context context) {
super(context);
}
public Rectangle(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public Rectangle(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
canvas.drawRect(400,200,800,600,paint);
}
}
Then
<com.redot.puzzle1.Rectangle
android:layout_width="match_parent"
android:layout_height="match_parent"/>
I have written a customized View which draws a circle(it's onDraw function has been overridden to do that).
Now how doI change the color of my circle, from code?(from the Activity function which is going to show that circle)
You can make a setCircleColor to change the color of the circle and call invalidate that will call the View onDraw method.
You can also check for invalidate(Drawable drawable).
public class MyCustomView extends View {
MyCustomView myView;
private Paint myCircle;
public MyCustomView(Context context){
super(context);
initView();
}
private void initView(){
myView = this;
myCircle = new Paint();
myCircle.setColor(0xa300ff00);
}
#Override
protected void onDraw(Canvas canvas) {
drawCircle(canvas);
}
private void drawCircle(Canvas canvas){
canvas.drawCircle(canvas.getWidth()/2, canvas.getHeight()/2, 10, myCircle);
}
public void setCircleColor(int color){
myCircle.setColor(color);
myView.invalidate();
}
}
Here's my OnDraw() method
void onDraw(Canvas canvas) {
mCanvas = canvas;
//invalidate();
int x = 0;
Iterator<Letter> it = mNextUpQueue.iterator();
while(it.hasNext()){
mCanvas.drawBitmap(it.next().getNext(), mNextUpCoordinates.get(x).x, mNextUpCoordinates.get(x).y, mPaint);
mCanvas.drawBitmap(mAvailableLetters.get(x).getNotPressed(), mAvailableLettersCoordinates.get(x).x, mAvailableLettersCoordinates.get(x).y, mPaint);
x++;
}
}
I have set canvas to a global variable mCanvas. But if I try to paint on mCanvas from outside the onDraw() method I get an error. Is it because I'm doing something wrong or the canvas must always be used from within the onDraw method?
You mustn't take the reference to the Canvas passed, as it is only valid during the onDraw(Canvas) method call.
I recommend reading http://developer.android.com/guide/topics/graphics/2d-graphics.html#draw-with-canvas thoroughly, the possible ways are explained there quite well:
To the Canvas provided to the onDraw(Canvas) method, during this method call. This is done in the UI thread, so nothing complicated should be attempted here
To a Canvas you created yourself. This could be useful for preparing a bitmap in another thread and then drawing the bitmap to the Canvas given to onDraw(Canvas) method
Using a SurfaceView, and obtaining a Canvas object from it with lockCanvas() method.
You can use invalidate(); to call onDraw() and draw canvas depending on your drawing logic.
Example
public class ThumbnailImage extends android.support.v7.widget.AppCompatImageView {
public static final int FALSE = 0, TRUE = 1, NOT_SET = 2;
private int drawingStatus;
public ThumbnailImage(Context context) {
super(context);
init();
}
public ThumbnailImage(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ThumbnailImage(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
...
drawingStatus = NOT_SET;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (drawingStatus != NOT_SET) {
if (drawingStatus == TRUE) {
...
} else {
...
}
}
}
public void setDrawingStatus(int drawingStatus) {
this.drawingStatus = drawingStatus;
invalidate();
}
}
I am trying to click a button and add a resistor. So what I need is to invalidate the view when the button is clicked. But the invalidate that is inside MyView inside the method update() is not working. I have been trying to search for this problem but I have found nothing similar to what I am trying to do, or maybe this is not the way to do it.
DefaultActivity.java
public class DefaulActivity extends Activity {
MyView myView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myView = new MyView(this, null);
final Button bAddResistor = (Button) findViewById(R.id.bAdd);
bAddResistor.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
myView.update();
Log.d("ButtonADD", "Button Add has been clicked");
}
});
}
}
MyView.java
public class MyView extends SurfaceView implements SurfaceHolder.Callback{
Resistor myResistor;
private ArrayList<Resistor> mElements = new ArrayList<Resistor>();
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
synchronized (mElements) {
for (Resistor element : mElements) {
element.doDraw(canvas);
}
}
}
public void update() {
mElements.add(new Resistor(getContext(), (int) 10, (int) 10));
invalidate(); //Does not work!
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = holder.lockCanvas();
onDraw(c);
holder.unlockCanvasAndPost(c);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
Resistor.java
public class Resistor extends View{
private Path mSymbol;
private Paint mPaint;
private int mX;
private int mY;
//...Override Constructors...
public Resistor(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Resistor(Context context, int x, int y){
super(context);
mX = x;
mY = y;
init();
}
private void init() {
mSymbol = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(2);
mPaint.setColor(-7829368);
mPaint.setStyle(Paint.Style.STROKE);
//...Your code here to set up the path,
//...allocate objects here, never in the drawing code.
mSymbol.moveTo(0.0F, 0.0F);
mSymbol.lineTo(0.0F, 50.0F);
mSymbol.lineTo(16.666666F, 58.333332F);
mSymbol.lineTo(-16.666666F, 75.0F);
mSymbol.lineTo(16.666666F, 91.666664F);
mSymbol.lineTo(-16.666666F, 108.33333F);
mSymbol.lineTo(16.666666F, 124.99999F);
mSymbol.lineTo(-16.666666F, 141.66666F);
mSymbol.lineTo(0.0F, 150.0F);
mSymbol.lineTo(0.0F, 200.0F);
mSymbol.offset(mX, mY);
}
public void doDraw(Canvas canvas) {
canvas.drawPath(mSymbol, mPaint);
}
You should use a ViewGroup instead of a SurfaceView. Add the Resistor-Views as children to MyView, measure and layout them appropriately and the ViewGroup will automatically take care of drawing them.
SurfaceView requires you to do your drawing in a seperate thread. You have to start that thread in surfaceCreated and keep redrawing the view as needed. For simple views like the one you posted it is perfectly acceptable to just do your drawing in the UI thread. No SurfaceView required.
I haven't used Surfaceviews before but if you in the API you can see, that there is no function called "invalidate()" for the SurfaceView class.
Maybe you can manage to re-use this line somehow:
holder.unlockCanvasAndPost(c);