• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps View's onDraw() stops being called after four touch events

AlexSCH

Lurker
Hi everybody!
I'am new to Android development and got stuck with one interesting issue.
I've started implementing my own tree-like view, which will expand/collapse its nodes as user taps on them. Every node in a tree is a custom TextView(just for now) and there is two included ViewGroups to hold those textviews together. ViewGroup forced me to override it's onLayoutMethod() which in my case will call layout method on each TextView node. Also used drawChild(Canvas canvas, View child, long drawingTime) to pass drawing from viewgroup to it's children. Every node(textview) implements View.OnTouchListener to handle touch events and calls parent's requestLayout() method to force re-layout of tree nodes. Everything seems to be working fine, but unfortunately for not so long )) Problem is that drawChild() method get's invoked by system only four times!:( Every next (on touch event) requestLayout() call executes onLayout() as always but drawChild() never gets invoked again, only app restart helps.
Application seems to be running normally and no exceptions are thrown into the console.
Can anyone help solving limited paints problem ? Will appreciate any comments or advices.

Here are sorces:
Node.java
Code:
package com.example;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.TextView;

import java.util.Vector;


public class Node extends TextView implements View.OnTouchListener
{
    private Object data;
    private Vector children;
    private Node parentNode;

    private boolean isExpanded = true;

    public Node(Context context)
    {
        super(context);
        setOnTouchListener(this);
    }

    public Node(Context context, Object data)
    {
        this(context);
        setData(data);
        if (data instanceof String)
            setText((String) data);
    }

    public Vector<Node> getChildren()
    {
        if (this.children == null)
        {
            return new Vector();
        }
        return this.children;
    }

    public void setChildren(Vector children)
    {
        if (children != null)
        {
            for (int i = 0; i < children.size(); i++)
            {
                ((Node) children.elementAt(i)).setParent(this);
            }
        }
        this.children = children;
    }

    public int getNumberOfChildren()
    {
        if (children == null)
        {
            return 0;
        }
        return children.size();
    }

    public void addChild(Node child)
    {
        if (children == null)
        {
            children = new Vector();
        }
        child.setParent(this);
        children.addElement(child);
    }

    public void insertChildAt(int index, Node child) throws IndexOutOfBoundsException
    {
        if (index == getNumberOfChildren())
        {
            // this is really an append
            addChild(child);
        }
        else
        {
            child.setParent(this);
            children.elementAt(index); //just to throw the exception, and stop here
            children.insertElementAt(child, index);
        }
    }

    public void removeChildAt(int index) throws IndexOutOfBoundsException
    {
        children.removeElementAt(index);
    }

    public Object getData()
    {
        return this.data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public boolean isExpanded()
    {
        return isExpanded;
    }

    public void setExpanded(boolean expanded)
    {
        isExpanded = expanded;
    }

    public void setParent(Node parentNode)
    {
        this.parentNode = parentNode;
    }

    public Node getParentNode()
    {
        return parentNode;
    }

    public boolean equals(Object obj)
    {
        if (obj instanceof Node)
        {
            Object objectData = ((Node) obj).data;
            if (data != null)
                return data.equals(objectData);
            else
                return objectData == null;
        }
        else
            return false;
    }

    public int hashCode()
    {
        if (data == null)
            return 0;
        else
            return data.hashCode();
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        System.out.println("in node onDraw");
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        canvas.drawText((String) getData(), getLeft() + 10, getBottom(), paint);
        if (!getChildren().isEmpty())
        {
            canvas.drawLine(getLeft(), getTop() + ((getBottom() - getTop()) / 2) + 1, getLeft() + 8, getTop() + ((getBottom() - getTop()) / 2) + 1, paint);
            if (!isExpanded)
                canvas.drawLine(getLeft() + 4, getTop() + 4, getLeft() + 4, getBottom(), paint);
        }       
    }

    public boolean onTouch(View view, MotionEvent motionEvent)
    {
        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN)
        {
            isExpanded = !isExpanded;
            System.out.println("Expanded=" + isExpanded);
            ViewParent viewParent = view.getParent();
            viewParent.requestLayout();
            return true;
        }
        return false;
    }
}
SimpleTree.java which holds Nodes
Code:
package com.example;


import android.content.Context;
import android.graphics.Canvas;
import android.view.ViewGroup;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

public class SimpleTree extends ViewGroup
{
    private Node rootElement;
   
    public SimpleTree(Context context)
    {
        super(context);
        rootElement = new Node(context, new Long(-1));
    }

    public Node getRootElement()
    {
        return this.rootElement;
    }

    public Vector toVector()
    {
        Vector list = new Vector();
        walk(rootElement, list);
        return list;
    }

    public String toString()
    {
        return toVector().toString();
    }

    private void walk(Node element, Vector list)
    {
        list.addElement(element);
        Vector children = element.getChildren();
        for (int i = 0; i < element.getNumberOfChildren(); i++)
        {
            walk((Node) children.elementAt(i), list);
        }
    }

    public Node findNode(Object data)
    {
        return findNode(rootElement, data);
    }

    private Node findNode(Node node, Object data)
    {
        if (node.getData().equals(data))
            return node;
        else
        {
            Vector children = node.getChildren();
            for (int i = 0; i < children.size(); i++)
            {
                Node res = findNode((Node) children.elementAt(i), data);
                if (res != null)
                    return res;
            }

            return null;
        }
    }

    public void insertToTreeAsChild(Node parent, Node bufferTaskNode)
    {
        addView(bufferTaskNode);
        Node parentNode = findNode(parent.getData());
        if (parentNode == null)
            parentNode = getRootElement();

        Node taskNode = findNode(bufferTaskNode.getData());
        if (taskNode == null)
            taskNode = bufferTaskNode;
        if (parentNode.isExpanded())
            parentNode.insertChildAt(0, taskNode);
        else
            parentNode.addChild(taskNode);
    }

    public void insertToTreeAsSibling(Node parent, Node bufferTaskNode, Node focusTaskNode)
    {
        addView(bufferTaskNode);
        Node parentNode;
        Node taskNode = findNode(bufferTaskNode.getData());
        if (taskNode == null)
            taskNode = bufferTaskNode;
        if (parent == null)
            parentNode = getRootElement();
        else
        {
            parentNode = findNode(parent.getData());

            if (parentNode == null)
                parentNode = getRootElement();
        }

        taskNode.setParent(parentNode);
        if (focusTaskNode != null)
        {
            for (int i = 0; i < parentNode.getNumberOfChildren(); i++)
            {
                String tempChild = (String) ((Node) parentNode.getChildren().elementAt(i)).getData();
                if (tempChild.equals(((String) focusTaskNode.getData())))
                {
                    parentNode.insertChildAt(i + 1, taskNode);
                    break;
                }
            }
        }
        else
            parentNode.insertChildAt(0, taskNode);
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        paintElements(canvas, getRootElement());
    }

    private void paintElements(Canvas canvas, Node node)
    {
        if (!(node.getData() instanceof Long))
        {
            node.draw(canvas);
        }
        if (node.getChildren() != null && node.getChildren().size() != 0 && node.isExpanded())
        {
            for (int i = 0; i < node.getChildren().size(); i++)
            {
                paintElements(canvas, node.getChildren().elementAt(i));
            }
        }
    }

    @Override
    protected void onLayout(boolean b, int left, int top, int right, int bottom)
    {
        layoutElements(getRootElement(), 0, 1);
    }

    private int layoutElements(Node node, int left, int top)
    {
        if (!(node.getData() instanceof Long))
        {
            node.layout(left * 15, top * 15, 50 + left * 15, top * 15 + (int) node.getPaint().getTextSize());
        }
        if (node.getChildren() != null && node.getChildren().size() != 0 && node.isExpanded())
        {
            top++;
            for (int i = 0; i < node.getChildren().size(); i++)
            {
                top = layoutElements(node.getChildren().elementAt(i), left + 1, top + i);
            }
        }
        return top;
    }

}
General TreeView which operates SimpleTree
Code:
public class TreeView extends ViewGroup
{
    SimpleTree tree;

    public TreeView(Context context)
    {
        super(context);
        tree = new SimpleTree(context);
        addView(tree);
        tree.insertToTreeAsSibling(tree.getRootElement(), new Node(context, "test1"), null);
        tree.insertToTreeAsChild(tree.findNode("test1"), new Node(context, "test11"));
        tree.insertToTreeAsChild(tree.findNode("test11"), new Node(context, "test111"));
        tree.insertToTreeAsChild(tree.findNode("test11"), new Node(context, "test112"));
        tree.insertToTreeAsSibling(tree.getRootElement(), new Node(context, "test2"), tree.findNode("test1"));
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3)
    {
        tree.onLayout(b, i, i1, i2, i3);        
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime)
    {
        tree.onDraw(canvas);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        return tree.dispatchTouchEvent(event);   
    }
}
and finally Activity class
Code:
public class MLOActivity extends Activity
{
    public static final String GROUP_ID = "Group";
    public static final String CHILD_ID = "Child";
    public static final int GROUPS = 2;
    public static final int CHILDREN = 2;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);       
        TreeView view = new TreeView(this);
        setContentView(view);
        view.invalidate();        
    }   
}
Thanks in advance.
 
Hi AlexSCH

If I get the chance I'll experiment with your code later. But for now, I can only make a suggestion.

Are the calls to drawChild happening when the application starts, or in response to a touch event? I suspect that it's just happening when the application draws itself for the first time, and the touch events are not having any effect on the drawing.

In your onTouch method you try to force a redraw by calling viewParent.requestLayout() - but maybe that isn't having the desired effect. Maybe you could try viewParent.invalidate() instead?

I haven't played with ViewGroups and can't try anything out at the moment. So I could be talking rubbish. But it might be worth a try, if you haven't tried it already.

Mark
 
Back
Top Bottom