Android SurfaceView не отображается onDraw

Для простоты вопроса я рисую целое число в SurfaceView, которое увеличивается на 1 при каждом рисовании. Увеличение действительно происходит, как я вижу на System.out. Текст на экране остается на «0». Кто может сказать мне, что я делаю неправильно?

SurfaceViewTest.java

package com.niek.surfaceviewtest;

import android.app.Activity;
import android.os.Bundle;

public class SurfaceViewTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

StatusView.java

package com.niek.surfaceviewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class StatusView extends SurfaceView implements SurfaceHolder.Callback {

    private int tmp;
    private DrawThread drawThread;

    public StatusView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
        setFocusable(true);
        drawThread = new DrawThread(getHolder());
    }

    @Override
    public void onDraw(Canvas c) {
            c.drawColor(Color.BLACK);
        Paint p = new Paint();
        p.setColor(Color.RED);
        c.drawText(tmp + "", 10, 10, p);
        tmp++;

        System.out.println(tmp);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // TODO Auto-generated method stub

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        drawThread.setRunning(true);
        drawThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        drawThread.setRunning(false);
        while (retry) {
            try {
                drawThread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }

    protected class DrawThread extends Thread {
        private SurfaceHolder surfaceHolder;
        private boolean isRunning;

        public DrawThread(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
            isRunning = false;
        }

        public void setRunning(boolean run) {
            isRunning = run;
        }

        public void run() {
            Canvas c;
            while (isRunning) {
                c = null;
                try {
                    c = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder) {
                        onDraw(c);
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        surfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FFFFFF">

    <com.niek.surfaceviewtest.StatusView
        android:id="@+id/statusview1"
        android:layout_width="fill_parent"
        android:layout_height="30dip"
        android:background="#000000"
 />
</LinearLayout>

person nhaarman    schedule 12.04.2011    source источник
comment
[thread-necro] Это не работает, потому что смешивает части Surface и View в SurfaceView. onDraw() используется для рисования в части View, но для SurfaceView View должен быть прозрачным отверстием (необходимым для кода макета), которое позволяет вам видеть независимый слой Surface позади. Первый вызов onDraw() очищает View до непрозрачного черного цвета, поэтому ничего, отображаемое на Surface, никогда не будет видно. Вышеприведенная реализация в сочетании с принятым ответом ниже приводит к пользовательскому представлению с ненужными накладными расходами. Указание фона для представления также является плохой идеей.   -  person fadden    schedule 09.09.2015


Ответы (3)


Мое единственное предположение состоит в том, что рисование не выполняется на поверхности, потому что представление не аннулируется. Вы должны вызвать invalidate(), чтобы для рисования, а затем пусть фреймворк вызовет onDraw(). Возможно, именно поэтому tmp увеличивается, но операция рисования достигает поверхности только в первый раз.

Возможно, стоит поэкспериментировать: может быть, сделать Canvas c членом StatusView, а затем заменить

synchronized (surfaceHolder) {
  onDraw(c);
}

с

synchronized (surfaceHolder) {
  invalidate();
}

Это работает?

person Steve Blackwell    schedule 12.04.2011
comment
Это действительно сработало. Но вместо invalidate() мне пришлось вызвать postInvalidate(), иначе было выброшено исключение CalledFromWrongThreadException. Все еще нахожу это странным, так как я в основном скопировал 90% этого из здесь - person nhaarman; 13.04.2011
comment
О Конечно. Это действительно так сказано в документации, на которую я ссылаюсь! Это должно быть вызвано из потока пользовательского интерфейса. Чтобы вызвать из потока, отличного от пользовательского интерфейса, вызовите postInvalidate(). Рад, что это сработало. - person Steve Blackwell; 13.04.2011
comment
Действительно ли вызов 'invalidate' нуждается в синхронизации? Разве это не просто говорит поверхности рисовать ее из своей собственной нити? - person noelicus; 05.08.2012
comment
@noelicus Да, я думаю, ты прав. Поскольку фактическим вызовом является postInvalidate(), все взаимодействие с SurfaceHolder будет происходить в потоке пользовательского интерфейса. Нужна ли вообще DrawThread ссылка на SurfaceHolder? Хороший улов. - person Steve Blackwell; 06.08.2012

Текущее решение неверно. Вы используете SurfaceView для обновления содержимого из отдельного потока и не используете метод invalidate(), который будет запускать метод onDraw(), когда система обновляет содержимое. Проблема в том, что вы установили фон для своего StatusView, попробуйте удалить эту строку

android:background="#000000"

очевидно, вам нужно контролировать всю информацию, отображаемую в этом представлении.

person petrica.martinescu    schedule 12.10.2011

Похоже, вы показываете свой main.xml с помощью setContentView(R.layout.main); вместо того, чтобы создавать поверхность и отображать ее. если я не пропускаю код где-то, я не вижу, что это так.

person noobS41bot    schedule 12.04.2011
comment
R.layout.main относится к XML-файлу (main.xml), в котором указано com.niek.surfaceviewtest.StatusView. Я думаю, это должно сработать. - person Steve Blackwell; 12.04.2011
comment
упс, мой плохой ... похоже, вы вызываете это в main.xml. Я тоже новичок в этом и не знал, что вы можете сделать это через xml. смешной - person noobS41bot; 12.04.2011