2

In my onDraw I have all code needed to build my entire view but how can I detect if I want to perform only a partial redraw. I guess a partial redraw should be triggered by calling canvas.invalidate(Rect rect); Right? In developer settings of my device I enabled “Show screen updates” but this always tells me that my entire screen is redrawn…

Below you see a screenshot of my app:

Screenshot of app

As you can see it is a calendar and I want to give the user a visual feedback when an entry is clicked (let’s say a red border around)…

I’ve already seen some samples but they either use a bitmap or lots of member variables to execute just the code needed for redrawing specific region in onDraw…

Can anyone tell me what’s the best way to implement such a feature?

UPDATE:

On my first draw Canvas.getClipBounds() returns the following rect:

Rect(0, 0 - 1200, 1800)

when I call invalidate(new Rect(304, 748 - 529, 902)) and check getClipBounds() again in onDraw() it still has the same value.

UPDATE2 (my code):

@Override
public boolean onTouch(View v, MotionEvent event) {

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {
        _pTouchDown = new PointF(event.getX(), event.getY());

        downX = event.getX();
        downY = event.getY();

        entrySelected = hasTimeRecordAt(downX, downY);
        if (entrySelected != null) {
            Rect rInvalidate = new Rect((int) entrySelected.get_Selected().left, (int) entrySelected.get_Selected().top, (int) entrySelected.get_Selected().right,
                    (int) entrySelected.get_Selected().bottom);

            invalidate(rInvalidate);


        } 

        return false;
    }

UPDATE 3 (my Layout):

<android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MultiDayCalendarActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical" >

        <RelativeLayout
            android:id="@+id/rlStatusline"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/tvStatusline1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:text="asdf" >
            </TextView>

            <TextView
                android:id="@+id/tvStatusline2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:text="1234" >
            </TextView>
        </RelativeLayout>

        <com.mxp.time.calendar.DayHeader
                android:id="@+id/dayHeader"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                 />

        <ScrollView
            android:id="@+id/m_svMultiRoot1"
            android:layout_width="match_parent"
            android:layout_height="0dip"
            android:layout_weight="1" >

            <com.mxp.time.calendar.Calendar
                android:id="@+id/calendar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                 />
        </ScrollView>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="1dp"
            android:background="@color/brushBackgroundLight" >
        </LinearLayout>

        <RelativeLayout
            android:id="@+id/rlMenu"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true" >

                <ImageButton
                    android:id="@+id/ibtCreateNewTimeRecord"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/menu" />

                <ImageButton
                    android:id="@+id/ibtCalendarStopwatch"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/stopwatch" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:orientation="horizontal" >

                <ImageButton
                    android:id="@+id/ibtCalendarBack"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/previous" />

                <ImageButton
                    android:id="@+id/ibtCalendarForward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/next" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true" >

                <ImageButton
                    android:id="@+id/ibtCalendarToday"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/today" />

                <ImageButton
                    android:id="@+id/ibtGotoJobs"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/jobs" />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>

    <FrameLayout
        android:id="@+id/drawer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start" >
    </FrameLayout>

</android.support.v4.widget.DrawerLayout>

UPDATE4:

setContentView(R.layout.test_calendar);
        // _cal = (Calendar) findViewById(R.id.calendar);
        _cal = new Calendar(this);

        _dayHeader = (DayHeader) findViewById(R.id.dayHeader);

        final ScrollView sv = (ScrollView) findViewById(R.id.m_svMultiRoot1);
        sv.addView(_cal);

same result:

I in onTouch I pass Rect(172, 748 - 265, 902) and in onDraw I get Rect(0, 0 - 720, 1800)

UPDATE 5:

package com.example.testclip;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

class V extends View {

private static final String TAG = "null";
Rect clip = new Rect();

public V(Context context) {
    super(context);
    int[] colors = { 0xff000000, 0xffff0000, 0xffffffff };
    Drawable d = new android.graphics.drawable.GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation.TOP_BOTTOM, colors);
    setBackgroundDrawable(d);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    int x = (int) event.getX();
    int y = (int) event.getY();
    StringBuilder sb = new StringBuilder();

    sb.append("left: ");
    sb.append(x);
    sb.append(", top: ");
    sb.append(y);

    sb.append("right: ");
    sb.append(x + 10);
    sb.append(", bottom: ");
    sb.append(y + 10);

    Log.d(TAG, "onTouchEvent  clip rect: " + sb.toString());

    invalidate(x, y, x + 10, y + 10);

    return false;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int w = MeasureSpec.getSize(widthMeasureSpec);
    setMeasuredDimension(w, w * 4);
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.getClipBounds(clip);
    StringBuilder sb = new StringBuilder();

    sb.append("left: ");
    sb.append(clip.left);
    sb.append(", top: ");
    sb.append(clip.top);

    sb.append("right: ");
    sb.append(clip.right);
    sb.append(", bottom: ");
    sb.append(clip.bottom);

    Log.d(TAG, "onDraw  clip rect: " + sb.toString());
}
}

Activity:

package com.example.testclip;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.ScrollView;

public class TestClipMainActivity extends Activity {

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

     ScrollView sv = new ScrollView(this);
        V v = new V(this);
        sv.addView(v);
        setContentView(sv);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.test_clip_main, menu);
    return true;
}

}

This code produces the following output

02-15 10:47:54.011: D/OpenGLRenderer(833): Enabling debug mode 0 02-15 10:47:54.926: D/dalvikvm(833): threadid=1: still suspended after undo (sc=1 dc=1) 02-15 10:48:03.806: D/null(833): onDraw clip rect: left: 0, top: 0right: 720, bottom: 2880 02-15 10:48:05.381: D/null(833): onDraw clip rect: left: 0, top: 0right: 720, bottom: 2880 02-15 10:48:07.181: D/null(833): onTouchEvent clip rect: left: 409, top: 358right: 419, bottom: 368 02-15 10:48:09.806: D/null(833): onDraw clip rect: left: 0, top: 0right: 720, bottom: 2880

stefan
  • 1,336
  • 3
  • 21
  • 46
  • Cant u just get the position of ur selected rectangle and draw a rectangle underneath it with say 10px more at every side, so it looks like a border? – Green_qaue Feb 11 '14 at 15:47
  • Canvas.getClipBounds ? – pskink Feb 11 '14 at 15:52
  • @iQue: thats exactly what I do. Actually I calculate all my rectangles outside the onDraw method so that I don't need to allocate any objects inside my onDraw. I have three rects: Border (black), Inner( the color of the entry) and selected ( the red one ). In on touch I check if the coordinates are in any rectangle and if so I perform a redraw and use the "selected" rect and not my "default" black border rect. I just don't want to redraw everything everytime... – stefan Feb 11 '14 at 16:01
  • @pskink getClipBounds always returns the same value. I updated my post whith that information – stefan Feb 11 '14 at 16:05
  • You can just use `Canvas.save()` Clip region you want and only draw here `Canvas.restore()` – Green_qaue Feb 11 '14 at 16:28
  • And how to detect when a partial redraw is needed in onDraw? – stefan Feb 11 '14 at 16:35

1 Answers1

3

you must be doing something wrong, see this custom View:

class V extends View {

    Rect clip = new Rect();
    private int cnt = 20;

    public V(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        cnt++;
        Log.d(TAG, "calling invalidate " + cnt);
        invalidate(10, 10, cnt, cnt);
        return false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.getClipBounds(clip);
        Log.d(TAG, "onDraw clip " + clip);
    }
}

UPDATE: custom view inside a ScrollView:

class V extends View {

    Rect clip = new Rect();

    public V(Context context) {
        super(context);
        int[] colors = {0xff000000, 0xffff0000, 0xffffffff};
        Drawable d = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
        setBackgroundDrawable(d);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        invalidate(x, y, x + 10, y + 10);
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int w = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(w, w * 4);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.getClipBounds(clip);
        Log.d(TAG, "onDraw clip height: " + clip.height());
    }
}

add this to onCreate:

    ScrollView sv = new ScrollView(this);
    V v = new V(this);
    sv.addView(v);
    setContentView(sv);
pskink
  • 23,874
  • 6
  • 66
  • 77
  • No, sorry! I call super.onDraw prior to getClipBounds(). Maybe this is the problem? Going to test this – stefan Feb 11 '14 at 16:54
  • 1
    Nope. I switched to my Samsung Note2 and called -> invalidate (Rect(172, 1219 - 265, 1383)) and the first line of my onDraw is: Rect rCLip = canvas.getClipBounds(); and this rCLip is -> Rect(0, 0 - 720, 1800). I extended my question with the code from my onTouch mehtod – stefan Feb 11 '14 at 17:04
  • when you click twice or mire the bounds are as expected, only the first two onDraws report full view bounds – pskink Feb 11 '14 at 17:15
  • I don't click twice or mire. I guess I already tried a thousand times to click ^^ I just removed all my code from my onTouch and called: invalidate(new Rect(10,10,10,10)); and in the first line of my onDraw I do canvas.getClipBounds but I don't get any clip. Can this problem be caused because my custom view is placed inside a scrollview? – stefan Feb 11 '14 at 17:32
  • Updated my post the third time, now I added my layout file. – stefan Feb 11 '14 at 17:36
  • ok, what if you do: v = new com.mxp.time.calendar.Calendar(this); setContentView(v); ? – pskink Feb 11 '14 at 17:46
  • Same result. I upated my question again with my test code. Added the calendar in code to my scrollview – stefan Feb 11 '14 at 18:04
  • for tesring set Calendar as the root view: setContenrView(calendarInstance) – pskink Feb 11 '14 at 18:22
  • Do you think I have the same problem? -> https://groups.google.com/forum/m/#!topic/android-developers/hE56nF8AfMg But my calendars onDraw isn't called twice. It is just called once with the full screen size. So if it works the way it is described in that link my partial invalidate must have been dropped an replaced by a full invlaidate – stefan Feb 11 '14 at 19:10
  • i dont know, but what you see in the logcat on your device if you run my updated code (with a ScrollView, see my updated answer)? – pskink Feb 11 '14 at 19:28
  • This is not working. It can't be than only I have this problem. I updated my question with the code I used (its 100% yours) and the coresponding logs. – stefan Feb 15 '14 at 09:49
  • try to click two or three times, are the results the same? – pskink Feb 15 '14 at 10:04
  • Touched 100 times. This does not work! And I guess I can tell you why this code is running when you run it. It is because you are using the emulator, right? Thats the only device where it is actually working. Just tried it on Galaxy Note 2, LG Optimus 4x HD, LG Gpad 8.3... – stefan Feb 15 '14 at 10:29
  • yes, i run it on emu, let me see on the real device – pskink Feb 15 '14 at 10:32
  • I also thought that my clip may be overridden by a invalidate that may be caused by the super implementation of the touch event itself. But this isn't the case. When I touch the screen and I don't add a invalidate to the onTouch event my onDraw is not called. So something else must override my invalidate. – stefan Feb 15 '14 at 10:46
  • bad news: i tested my code on my poor, low end htc and i got "clip height 10" – pskink Feb 15 '14 at 11:00
  • Additionaly I tested it on Sony Xperia Z1 and Galaxy S3 and it is not working. When you update your answer in "Is not working on every device (most likely newer ones)" I will mark it as answer. Maybe this will help soneone else. Many thanks for your assistance!!! – stefan Feb 15 '14 at 11:26
  • How can I evaluate result from this? I just enabled "Show screen updates" in my device settings and this shows me that the the entire view is redrawn when I touch... Are there any logs? Where to find them? – stefan Feb 15 '14 at 11:56
  • just use logcat, btw i found this https://groups.google.com/forum/m/#!topic/android-developers/wHkKLy6wIVk maybe the issue has something ro do with. hardware acceleration? – pskink Feb 15 '14 at 12:09
  • Got it! Kind of tricky one!! When I add this: android:hardwareAccelerated="false" I get the right clip – stefan Feb 15 '14 at 12:11
  • So, now I'm wiser but still don't know what to do. What is better for performance: disabling hardwareAcceleration, draw and run only the code which is needed for a partial redraw or allow hardwareAcceleration and let android draw everything everytime again? Since this question has already an enormous lenght I will ask this in a new question... – stefan Feb 15 '14 at 12:15
  • Please put this info to your answer so that I can mark it as answer. Here is my further question: http://stackoverflow.com/questions/21797497/hardwareaccelerated-true-vs-partial-drawing – stefan Feb 15 '14 at 12:26