0

I have a fragment with a relative layout - which is correctly displayed. Now I try to add a custom view with canvas - where I can dynamically draw on. But the onDraw-method of the custom view is not called if I call the drawConnectionLine method from the custom View. If I do not call the drawConnectionLine method it works. I tried to call the drawConnectionLine in the onResume-method after the view is created but it does not help...

How can I dynamically draw on the custom view after initialization without a NullPointerException(because of the onDraw not being executed)?

Here is my fragment-code(FragmentPlan.java):

package de.tucais.svm.youplan;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import android.app.Activity;
import android.app.Fragment;
import android.content.ContentValues;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class FragmentPlan extends Fragment
{
    // Define logging variables
    private static final String LOGCAT = "YP-FragmentPlan";
    private static final boolean D = true; // control debug-output

    Activity parentActivity;
    LibSVM svm = null;
    DatabaseController db;
    Controller controller = null;
    DrawView drawView = null;
    //Canvas drawCanvas = null;

    int firstTv_margin_top, firstTv_margin_left, firstTv_margin_right, firstTv_margin_bottom;
    int tv_margin_top, tv_margin_left, tv_margin_right, tv_margin_bottom;

    RelativeLayout planLayout = null;
    List<TextView> taskElements = new ArrayList<TextView>();

    public FragmentPlan()
    {

    }

    public void onResume()
    {
        super.onResume();
        computeResult();
    }

    @Override    
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.fragment_plan, container, false);
        controller = new Controller((MainActivity)getActivity());
        planLayout = (RelativeLayout)view.findViewById(R.id.relativeLayout_plan);

        // Translate the margin-values from dp to pixel
        firstTv_margin_left = getResources().getDimensionPixelSize(R.dimen.plan_firstTv_margin_left);
        firstTv_margin_top = getResources().getDimensionPixelSize(R.dimen.plan_firstTv_margin_top);
        firstTv_margin_right = getResources().getDimensionPixelSize(R.dimen.plan_firstTv_margin_right);
        firstTv_margin_bottom = getResources().getDimensionPixelSize(R.dimen.plan_firstTv_margin_bottom);
        tv_margin_left = getResources().getDimensionPixelSize(R.dimen.plan_tv_margin_left);
        tv_margin_top = getResources().getDimensionPixelSize(R.dimen.plan_tv_margin_top);
        tv_margin_right = getResources().getDimensionPixelSize(R.dimen.plan_tv_margin_right);
        tv_margin_bottom = getResources().getDimensionPixelSize(R.dimen.plan_tv_margin_bottom);

        // Create the DrawView and append it to the RelativeLayout
        drawView = new DrawView((MainActivity)getActivity());
        RelativeLayout.LayoutParams drawViewParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        drawView.setLayoutParams(drawViewParams);
        drawView.setId(View.generateViewId());

        planLayout.addView(drawView);
        //sendViewToBack(drawView);     
        if(D) Log.i(LOGCAT,"drawCanvas created...");

        if(D) Log.i(LOGCAT,"onCreateView finished...");
        return view;
    }

    private void computeResult()
    {
        List<ContentValues> result = controller.computeSequence();
        //displayResult(result.get(0));
        for(int i=0; i < result.size(); i++)
        {
            displayResultElement(result.get(i));
            if(i > 0)
            {
                if(D) Log.i(LOGCAT,"tried to draw connection for " + i + " time...");
                if(D) Log.i(LOGCAT,"DrawCanvas is null: " + drawView.isCanvasNull()); //-> returns true
                drawView.drawConnectionLine(taskElements, i); //-> this call causes the error
                if(D) Log.i(LOGCAT,"succesfully drawed connection nr" + i + "...");
            }
        }
    }

    private void displayResultElement(ContentValues resultElement)
    {
        // Create a new element
        TextView newElement = new TextView((MainActivity)getActivity());
        newElement.setId(View.generateViewId());
        newElement.setBackground(getResources().getDrawable(R.drawable.shape_circle));

        // Fill the new Element with its content
        Set<Entry<String, Object>> s = resultElement.valueSet();
        Iterator<Entry<String, Object>> itr = s.iterator();
        Map.Entry<String, Object> me = (Map.Entry<String, Object>)itr.next();
        newElement.append(me.getKey().toString()+": " + me.getValue().toString());
        while(itr.hasNext())
        {
            me = (Map.Entry<String, Object>)itr.next();
            newElement.append("\n"+me.getKey().toString()+": " + me.getValue().toString());
        }
        // After filling the new element with content append it to its parent view element
        planLayout.addView(newElement);
        // add the element to the list
        taskElements.add(newElement);
        // Position the new element on the screen       
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)newElement.getLayoutParams();
        // if there is more than the current element in the list position relative to the predecessor
        if(taskElements.size() > 1)
        {
            params.addRule(RelativeLayout.BELOW, taskElements.get(taskElements.size()-2).getId());
            params.addRule(RelativeLayout.ALIGN_LEFT, taskElements.get(taskElements.size()-2).getId());
            params.setMargins(tv_margin_left, tv_margin_top, tv_margin_right, tv_margin_bottom);
        }
        else
        {
            params.setMargins(firstTv_margin_left, firstTv_margin_top, firstTv_margin_right, firstTv_margin_bottom);
        }       
        newElement.setLayoutParams(params);
    }

    private static void sendViewToBack(final View v)
    {
        final ViewGroup parent = (ViewGroup)v.getParent();
        if (parent != null)
        {
            parent.removeView(v);
            parent.addView(v, 0);
        }
    }
}

This is my xml-file for the relativeLayout(fragment_plan.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/relativeLayout_plan"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="2dp"
    tools:context=".MainActivity" >

</RelativeLayout>

Here is my CustomView(DrawView.java):

package de.tucais.svm.youplan;

import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class DrawView extends View
{
    // Define logging variables
    private static final String LOGCAT = "YP-DrawView";
    private static final boolean D = true; // control debug-output

    private Canvas drawCanvas = null;

    public DrawView(final Context context)
    {
        super(context);
        if(D) Log.i(LOGCAT,"DrawView constructor called... ");
    }

    @Override
    protected void onMeasure(int w, int h)
    {
        if(D) Log.d(LOGCAT,"onMeasure with w= " + w + " and h= " + h + " called...");
        setMeasuredDimension(w, h);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        if(D) Log.i(LOGCAT,"onDraw called... ");
        this.drawCanvas = canvas;
        if(D) Log.d(LOGCAT,"drawCanvas still empty? " + (this.drawCanvas == null));
    }

    /*
     * Method which draws a connection line between two graphical elements on the screen
     * @param {List<TextView>} taskElements - a list of all TextView-elements
     * @param {int} nr - the number of the destination element
     */
    public void drawConnectionLine(List<TextView> taskElements, int nr)
    {
        if(D) Log.i(LOGCAT,"drawConnectionLine called... ");

        TextView currentElement = taskElements.get(nr);
        TextView prevElement = null;
        try
        {
            prevElement = taskElements.get(nr-1);
        }
        catch(Exception e)
        {
            Log.e(LOGCAT, "No predacessor!!!");
        }

        int startX = computeCenter(prevElement);
        int startY = prevElement.getBottom();
        int stopX = computeCenter(currentElement);
        int stopY = currentElement.getTop();

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        this.drawCanvas.drawLine(startX, startY, stopX, stopY, paint); //-> this.drawCanvas == null => Why???
    }

    private int computeCenter(View element)
    {
        int result = 0;
        if(element != null)
        {
            int right = element.getRight();
            result = right - (element.getWidth()/2);
        }
        return result;
    }

    public void emptyCanvas()
    {
        // Clear the canvas
        this.drawCanvas.drawColor(Color.TRANSPARENT);
    }

    public boolean isCanvasNull()
    {
        return this.drawCanvas == null;
    }
}

It would be very nice, if someone can explain me, why the onDraw-method of the DrawView is not called when calling the drawConnectionLine method- the reason for a NullPointerException in the FragmentPlan.java line 95.

Thanks in advance!!!

Lessip
  • 1
  • 3
  • Since you are creating an instance of DrawView from code using the basic constructor, it is possible that the DrawView has no LayoutParams and thus zero width and zero height. I would try including the DrawView in the layout directly, set a fixed width/height and see if it shows up. – Alex Fu Aug 22 '14 at 16:43
  • Thanks for the hint - after setting the height and width it is working. – Lessip Aug 23 '14 at 17:40
  • At least it is working as long as I do not call the drawConnectionLine-method in the custom view... – Lessip Aug 27 '14 at 13:03

1 Answers1

0

Yeah it seems like system assumes the view width and height to be zero and thus did not called on onDraw(). You can override the onMeasure(int, int) method in View class to specify the width and height of your view.

Link: Custom onDraw() method not called

Community
  • 1
  • 1
user3806339
  • 144
  • 1
  • 2
  • I added the measures (width & height) and it is working at least as long as I do not call the drawConnectionLine-method in the custom view... – Lessip Aug 27 '14 at 13:04
  • What is the problem if you call drawConnectionLine – user3806339 Aug 27 '14 at 14:19
  • The problem is, that this.drawCanvas in the last line of the method "drawConnectionLine" is null - and this causes a NullPointerException... – Lessip Aug 28 '14 at 17:11
  • It's most likely that you are calling canvas from outside the onDraw which means it is null. Try putting any drawing inside the onDraw function. When you want to redraw the canvas you have to call invalidate(). This will in turn call onDraw to redraw the canvas – user3806339 Sep 09 '14 at 17:46