0

I have created a subclass of View which is used to draw triangles. The TriangleDrawView is show below.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat;
import android.util.AttributeSet;
import android.view.View;

public class TriangleDrawView extends View {

    private Paint mPaint;
    private Path mPath;
    private int mDrawColor;
    private int mBackgroundColor;
    private Canvas mExtraCanvas;
    private Bitmap mExtraBitmap;

    private int paddingX, paddingY;
    private int drawableRectX,drawableRectY;
    private int triangleBoundX,triangleBoundY;
    private int total_width,total_height;
    private double angle_a,angle_b,angle_c;
    private int xA,xB,xC,yA,yB,yC;

    public TriangleDrawView(Context context) {
        this(context,null);
    }

    public TriangleDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mBackgroundColor = ResourcesCompat.getColor(getResources(),
                R.color.opaque_orange, null);
        mDrawColor = ResourcesCompat.getColor(getResources(),
                R.color.opaque_yellow, null);

        // Holds the path we are currently drawing.
        mPath = new Path();
        // Set up the paint with which to draw.
        mPaint = new Paint();
        mPaint.setColor(mDrawColor);
        // Smoothes out edges of what is drawn without affecting shape.
        mPaint.setAntiAlias(true);
        // Dithering affects how colors with higher-precision device
        // than the are down-sampled.
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE); // default: FILL
        mPaint.setStrokeJoin(Paint.Join.ROUND); // default: MITER
        mPaint.setStrokeCap(Paint.Cap.ROUND); // default: BUTT
        mPaint.setStrokeWidth(5); // default: Hairline-width (really thin)

        angle_a = Math.PI/3;
        angle_b = Math.PI/3;
        angle_c = Math.PI/3;

        paddingX = 100;
        paddingY = 100;
    }


    @Override
    protected void onSizeChanged(int width, int height,
                                 int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);

        // Create bitmap, create canvas with bitmap, fill canvas with color.
        mExtraBitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        mExtraCanvas = new Canvas(mExtraBitmap);
        // Fill the Bitmap with the background color.
        mExtraCanvas.drawColor(mBackgroundColor);

        total_width = width;
        total_height = height;
        drawTriangle(angle_a,angle_b,angle_c);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Draw the bitmap that stores the path the user has drawn.
        // Initially the user has not drawn anything
        // so we see only the colored bitmap.
        canvas.drawBitmap(mExtraBitmap, 0, 0, null);
    }


    private void initTriangleBounds(double angle_b, double angle_c, int total_width,int total_height, int paddingX, int paddingY){
        drawableRectX = total_width-2*paddingX;
        drawableRectY=total_height-2*paddingY;

        int x_,y_;
        x_ = total_width/2;
        y_ = total_height/2;

        if(angle_b<Math.PI/2 && angle_c<Math.PI/2 ){
            if(drawableRectX >= ( drawableRectY/Math.tan(angle_b) + drawableRectY/Math.tan(angle_c) ) ){
                triangleBoundY =  drawableRectY;
                triangleBoundX = (int)( drawableRectY/Math.tan(angle_b) + drawableRectY/Math.tan(angle_c) );
            }else{
                triangleBoundX = drawableRectX;
                triangleBoundY = (int) ( drawableRectX/( 1/Math.tan(angle_b) +1/Math.tan(angle_c) ) );
            }

            xB = x_ - triangleBoundX/2;
            yB = y_ + triangleBoundY/2;

            xC = x_ + triangleBoundX/2;
            yC = yB;

            yA = y_ - triangleBoundY/2;
            xA = xB + (int)(triangleBoundY/Math.tan(angle_b));

        }else if(angle_b>=Math.PI/2){
            if( drawableRectX <= ( drawableRectY/Math.tan(angle_c) )){
                triangleBoundY =  drawableRectY;
                triangleBoundX = (int)( drawableRectY/Math.tan(angle_c));
            }else{
                triangleBoundX = drawableRectX;
                triangleBoundY = (int)( drawableRectX * Math.tan(angle_c));
            }

            xA = x_ - triangleBoundX/2;
            yA = y_ - triangleBoundY/2;

            xC = x_ + triangleBoundX/2;
            yC = y_ + triangleBoundY/2;

            xB = xA + (int)(triangleBoundY/Math.tan(Math.PI - angle_b));
            yB = yC;
        }else if(angle_c>=Math.PI/2){
            if( drawableRectX <= ( drawableRectY/Math.tan(angle_b) )){
                triangleBoundY =  drawableRectY;
                triangleBoundX = (int)( drawableRectY/Math.tan(angle_b));
            }else{
                triangleBoundX = drawableRectX;
                triangleBoundY = (int)( drawableRectX * Math.tan(angle_b));
            }

            xA = x_ + triangleBoundX/2;
            yA = y_ - triangleBoundY/2;

            xB = x_ - triangleBoundX/2;
            yB = y_ + triangleBoundY/2;

            xC = xA - (int)(triangleBoundY/Math.tan(Math.PI - angle_c));
            yC = yB;
        }
    }

    public void drawTriangle(double angle_a, double angle_b, double angle_c){
        this.angle_a = angle_a;
        this.angle_b = angle_b;
        this.angle_c = angle_c;
        initTriangleBounds(angle_b,angle_c,total_width,total_height,paddingX,paddingY);

        mPath.moveTo(xA, yA);
        mPath.quadTo(xA,yA,xB,yB);
        mPath.quadTo(xB,yB,xC,yC);
        mPath.quadTo(xC,yC,xA,yA);
        mExtraCanvas.drawPath(mPath, mPaint);
        invalidate();
    }


}

Now, this TriangleDrawView is included in an activity as shown below.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_height="match_parent"
    tools:context="my.canvas2.TestingActivity">

    <view
        class="my.canvas2.TriangleDrawView"
        id="@+id/viewTriangle"
        android:layout_width="273dp"
        android:layout_height="302dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:onClick="onClick"/>
</android.support.constraint.ConstraintLayout>

When the activity started, it works fine as shown in the screenshot below. enter image description here

What I need to do is call the public void drawTriangle(double angle_a, double angle_b, double angle_c) method of the TriangleDrawView. Therefore, in the button's onClick method in the Activity I called the drawTriangle method as shown below.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class TestingActivity extends AppCompatActivity {

    private TriangleDrawView triangleDrawView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_testing);


        triangleDrawView = findViewById(R.id.viewTriangle);
    }

    public void onClick(View view) {
        double pi = Math.PI;
        //Method implemented
        triangleDrawView.drawTriangle(pi/2,pi/3,pi/6);
    }
}

But when the button of is clicked the app crashed saying about a NullPointerException. Here is the LogCat output;

FATAL EXCEPTION: main
   Process: my.canvas2, PID: 15645
   java.lang.IllegalStateException: Could not execute method for android:onClick
       at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
       at android.view.View.performClick(View.java:6294)
       at android.view.View$PerformClick.run(View.java:24770)
       at android.os.Handler.handleCallback(Handler.java:790)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:164)
       at android.app.ActivityThread.main(ActivityThread.java:6494)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
    Caused by: java.lang.reflect.InvocationTargetException
       at java.lang.reflect.Method.invoke(Native Method)
       at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
       at android.view.View.performClick(View.java:6294) 
       at android.view.View$PerformClick.run(View.java:24770) 
       at android.os.Handler.handleCallback(Handler.java:790) 
       at android.os.Handler.dispatchMessage(Handler.java:99) 
       at android.os.Looper.loop(Looper.java:164) 
       at android.app.ActivityThread.main(ActivityThread.java:6494) 
       at java.lang.reflect.Method.invoke(Native Method) 
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void my.canvas2.TriangleDrawView.drawTriangle(double, double, double)' on a null object reference
       at my.canvas2.TestingActivity.onClick(TestingActivity.java:24)
       at java.lang.reflect.Method.invoke(Native Method) 
       at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
       at android.view.View.performClick(View.java:6294) 
       at android.view.View$PerformClick.run(View.java:24770) 
       at android.os.Handler.handleCallback(Handler.java:790) 
       at android.os.Handler.dispatchMessage(Handler.java:99) 
       at android.os.Looper.loop(Looper.java:164) 
       at android.app.ActivityThread.main(ActivityThread.java:6494) 
       at java.lang.reflect.Method.invoke(Native Method) 
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 

Update

The NullPointerException is well explained in this question. But it didn't solved my problem. I successfully solved the problem by the answers by Mike M and Tomas Jablonskis.

The id attribute on the element must have the android prefix. That is, android:id="@+id/viewTriangle". I'd be surprised if your IDE isn't giving at least a warning about that.

-Mike M

Your view triangleDrawView is not being initialized, because you use id attribute in your XML on element, that is a reason why .findViewById(id) does not find and initialize your View properly.

Changing id=... to android:id=... should fix your problem.

-Tomas Jablonskis

Tharindu Sathischandra
  • 1,654
  • 1
  • 15
  • 37
  • 2
    The `id` attribute on the `` element must have the `android` prefix. That is, `android:id="@+id/viewTriangle"`. I'd be surprised if your IDE isn't giving at least a warning about that. – Mike M. Apr 07 '18 at 03:54
  • 2
    Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – akhilesh0707 Apr 07 '18 at 03:59
  • @akhilesh0707 The NullPointerException is well explained in this question. But it didn't solved my problem. – Tharindu Sathischandra Apr 07 '18 at 04:23

2 Answers2

1

Your view triangleDrawView is not being initialized, because you use id attribute in your XML on <view ... > element, that is a reason why .findViewById(id) does not find and initialize your View properly.

Changing id=... to android:id=... should fix your problem.

Tomas Jablonskis
  • 4,246
  • 4
  • 22
  • 40
1

Use debugging to rectify the issue. Some values were null so it gave null pointer exception. Add the debug break point in drawTraingle() method and check which value is null.

Amit Jangid
  • 2,741
  • 1
  • 22
  • 28