Android, холст: как очистить (удалить содержимое) холста (= растровые изображения), живущего в SurfaceView?

96

Чтобы сделать простую игру, я использовал шаблон, который рисует холст с такими растровыми изображениями:

private void doDraw(Canvas canvas) {
    for (int i=0;i<8;i++)
        for (int j=0;j<9;j++)
            for (int k=0;k<7;k++)   {
    canvas.drawBitmap(mBits[allBits[i][j][k]], i*50 -k*7, j*50 -k*7, null); } }

(Холст определяется в "run ()" / SurfaceView живет в GameThread.)

Мой первый вопрос: как очистить (или перерисовать) весь холст для нового макета?
Во-вторых, как я могу обновить только часть экрана?

// This is the routine that calls "doDraw":
public void run() {
    while (mRun) {
        Canvas c = null;
        try {
            c = mSurfaceHolder.lockCanvas(null);
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) 
                    updateGame();
                doDraw(c);          }
        } finally {
            if (c != null) {
                mSurfaceHolder.unlockCanvasAndPost(c);  }   }   }       }
samClem
источник

Ответы:

78

Как очистить (или перерисовать) ВСЕ холст для нового макета (= попробовать в игре)?

Просто позвоните Canvas.drawColor(Color.BLACK)или любым другим цветом, которым вы хотите очистить свой цвет Canvas.

И: как я могу обновить только часть экрана?

Нет такого метода, который просто обновляет «часть экрана», поскольку ОС Android перерисовывает каждый пиксель при обновлении экрана. Но когда вы не очищаете старые рисунки на своем Canvas, старые рисунки все еще остаются на поверхности, и это, вероятно, один из способов «обновить только часть» экрана.

Итак, если вы хотите «обновить часть экрана», просто избегайте вызова Canvas.drawColor()метода.

Вроцлай
источник
4
Нет, если вы не рисуете каждый пиксель на поверхности, вы получите очень странные результаты (потому что двойная буферизация достигается заменой указателей, поэтому в тех частях, где вы не рисуете, вы не увидите то, что было раньше). Вам нужно перерисовывать каждый пиксель поверхности на каждой итерации.
Гийом Брунери
2
@Viktor Lannér: Если вы позвоните, mSurfaceHolder.unlockCanvasAndPost(c)а затем c = mSurfaceHolder.lockCanvas(null), то новое cне будет содержать того же, что и предыдущее c. Вы не можете обновить только часть SurfaceView, о чем, я думаю, спрашивал OP.
Гийом Брунери
1
@ Гийом Брунери: Верно, но не все. Как я уже писал, часть экрана обновить нельзя. Но вы можете оставить старые рисунки на экране, что будет иметь эффект просто «обновить часть экрана». Попробуйте сами в образце приложения. Canvasхранит старые чертежи.
Wroclai
2
МНЕ СТЫДНО !!! Я инициализировал свой массив только ОДИН РАЗ при запуске игры, а НЕ при последующих перезапусках - поэтому ВСЕ СТАРЫЕ части остались «на экране» - они просто были нарисованы заново !!! ОЧЕНЬ ИЗВИНИТЕ мою "тупость"! Спасибо вам обоим !!!
samClem
1
Вероятно, это связано с тем, что живые обои не работают так же, как обычные SurfaceView. В любом случае, @samClem: всегда перерисовывайте каждый пиксель холста в каждом кадре (как указано в документе), иначе у вас будет странное мерцание.
Гийом Брунери
280

Рисование прозрачным цветом в режиме очистки PorterDuff делает то, что я хотел.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Силерия
источник
2
Это должно работать, но, похоже, в 4.4 (по крайней мере, на N7.2) используется ошибка Bitmap#eraseColor(Color.TRANSPARENT), как в ответе HeMac ниже.
nmr
4
Фактически, Color.TRANSPARENTэто не нужно. PorterDuff.Mode.CLEARвполне достаточно для ARGB_8888растрового изображения, что означает установку альфа и цвета на [0, 0]. Другой способ - использовать Color.TRANSPARENTс PorterDuff.Mode.SRC.
jiasli
1
У меня не получается оставлять пустую поверхность вместо прозрачной
pallav bohara
31

Нашел это в группах Google, и это сработало для меня ..

Paint clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(0, 0, width, height, clearPaint); 

Это удаляет прямоугольники чертежей и т. Д., Сохраняя при этом установленное растровое изображение.

Рукмал Диас
источник
4
Это дает мне черную область, а не то растровое изображение, которое у меня стоит за ней :(
slott
Он отлично очищается, но растровое изображение также удаляется, в отличие от того, что вы сказали.
Омар Рехман
в stackoverflow.com/questions/9691985/… объясняется, как нарисовать прямоугольник растрового изображения на некотором прямоугольнике холста. Таким образом, изменение изображения в прямоугольнике работает: изучите предыдущее содержимое, нарисуйте новое изображение
Мартин
18

используйте метод сброса класса Path

Path.reset();
Ракеш
источник
это действительно лучший ответ, если вы используете путь. другие могут оставить у пользователя черный экран. Спасибо.
j2emanue
18

Я попробовал ответ @mobistry:

canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

Но у меня это не сработало.

Решение для меня было:

canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);

Может у кого-то такая же проблема.

Дерзу
источник
4
Ответ - умножение на прозрачность. В противном случае на некоторых устройствах он может окраситься в черный цвет.
Андреас Рудольф
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
источник
2
как это наиболее правильно? зачем вообще использовать растровое изображение, если можно просто drawColor?
RichieHH 02
Хотя это может не сработать для исходного плаката (у них не обязательно есть доступ к растровому изображению), это определенно полезно для очистки содержимого растрового изображения, когда оно доступно. В таких случаях требуется только первая строка. Полезная техника, +1
голова в кодах
4

пожалуйста, вставьте ниже код в конструктор класса расширения surfaceview .............

кодирование конструктора

    SurfaceHolder holder = getHolder();
    holder.addCallback(this);

    SurfaceView sur = (SurfaceView)findViewById(R.id.surfaceview);
    sur.setZOrderOnTop(true);    // necessary
    holder = sur.getHolder();
    holder.setFormat(PixelFormat.TRANSPARENT);

xml кодирование

    <com.welcome.panelview.PanelViewWelcomeScreen
        android:id="@+id/one"
        android:layout_width="600px"
        android:layout_height="312px"
        android:layout_gravity="center"
        android:layout_marginTop="10px"
        android:background="@drawable/welcome" />

попробуйте код выше ...

Хемант Чанд Дунгриял
источник
Holder.setFormat (PixelFormat.TRANSPARENT); Меня устраивает.
Аарон Ли
3

Вот код минимального примера, показывающий, что вам всегда нужно перерисовывать каждый пиксель холста в каждом кадре.

Это действие рисует новое растровое изображение каждую секунду в SurfaceView без предварительной очистки экрана. Если вы его протестируете, вы увидите, что растровое изображение не всегда записывается в один и тот же буфер, и экран будет попеременно переключаться между двумя буферами.

Я тестировал его на своем телефоне (Nexus S, Android 2.3.3) и на эмуляторе (Android 2.2).

public class TestCanvas extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new TestView(this));
    }
}

class TestView extends SurfaceView implements SurfaceHolder.Callback {

    private TestThread mThread;
    private int mWidth;
    private int mHeight;
    private Bitmap mBitmap;
    private SurfaceHolder mSurfaceHolder;

    public TestView(Context context) {
        super(context);
        mThread = new TestThread();
        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon);
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        mWidth = width;
        mHeight = height;
        mThread.start();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {/* Do nothing */}

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mThread != null && mThread.isAlive())
            mThread.interrupt();
    }

    class TestThread extends Thread {
        @Override
        public void run() {
            while (!isInterrupted()) {
                Canvas c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {
                        c.drawBitmap(mBitmap, (int) (Math.random() * mWidth), (int) (Math.random() * mHeight), null);
                    }
                } finally {
                    if (c != null)
                        mSurfaceHolder.unlockCanvasAndPost(c);
                }

                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    interrupt();
                }
            }
        }   
    }
}
Гийом Брунери
источник
Что ж, похоже, вы ошибаетесь, посмотрите мой снимок экрана здесь: img12.imageshack.us/i/devicey.png/# Когда вы добавили задержку в одну секунду, двойная буферизация стала заметнее, но (!) на экране все еще есть предыдущие рисунки. Кроме того, ваш код неверен: так должно быть SurfaceHolder.Callback, а не просто так Callback.
Wroclai
1
Я думаю, вы не понимаете, о чем я. Можно было ожидать, что разница между кадром n и кадром n + 1 состоит в том, что есть еще одно Bitmap. Но это совершенно неправильно, есть еще одно Bitmap между кадром n и кадром n + 2 , но кадр n и кадр n + 1 совершенно не связаны, даже если я просто добавил Bitmap.
Гийом Брунери
Нет, я вас полностью понимаю. Но, как видите, ваш код не работает. Через некоторое время экран заполняется значками. Следовательно, нам нужно очистить, Canvasесли мы хотим полностью перерисовать весь экран. Это делает мое утверждение о «предыдущих рисунках» верным. CanvasСохраняет предыдущие рисунки.
Wroclai
4
Да, если хотите, Canvasсохраняет предыдущие рисунки. Но это совершенно бесполезно, проблема в том, что при использовании lockCanvas()вы не знаете, что это за «предыдущие рисунки», и не можете ничего предполагать о них. Возможно, если есть два действия с SurfaceView одинакового размера, они будут использовать один и тот же Canvas. Возможно, вы получите кусок неинициализированной оперативной памяти со случайными байтами в ней. Возможно, на холсте всегда есть логотип Google. Вы не можете знать. Любое приложение, которое не рисует каждый пиксель после lockCanvas(null), ломается.
Гийом Брунери,
1
@GuillaumeBrunerie спустя 2,5 года после того, как я наткнулся на ваш пост. Официальная документация Android поддерживает ваш совет относительно «рисования каждого пикселя». Есть только один случай, когда часть холста гарантированно останется на месте при последующем вызове lockCanvas (). Обратитесь к документации Android для SurfaceHolder и его двух методов lockCanvas (). developer.android.com/reference/android/view/… и developer.android.com/reference/android/view/…
Дата обновления:
3

Для меня звонок Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)или что-то подобное будет работать только после того, как я коснусь экрана. Итак, я бы назвал приведенную выше строку кода, но экран очистился бы только после того, как я коснулся экрана. Итак, что сработало для меня, так это вызов, invalidate()а затем, init()который вызывается во время создания для инициализации представления.

private void init() {
    setFocusable(true);
    setFocusableInTouchMode(true);
    setOnTouchListener(this);

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(6);

    mCanvas = new Canvas();
    mPaths = new LinkedList<>();

    addNewPath();
}
Джейсон Кросби
источник
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
алиакбарян
источник
2
Добавьте объяснение того, как ваш код решает проблему. Это сообщение отмечено как низкое качество - Из обзора
Сурадж Рао
1

Стирание на Canvas в java android аналогично стиранию HTML Canvas через javascript с помощью globalCompositeOperation. Логика была похожей.

U выберет логику DST_OUT (Destination Out).

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

Примечание: DST_OUT более полезен, потому что он может стереть 50%, если цвет краски имеет 50% альфа. Итак, чтобы полностью очистить до прозрачности, альфа цвета должна быть 100%. Рекомендуется применить paint.setColor (Color.WHITE). И убедитесь, что формат изображения холста был RGBA_8888.

После стирания вернитесь к нормальному рисованию с помощью SRC_OVER (Source Over).

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

Обновление дисплея с небольшой площадью буквально потребует доступа к графическому оборудованию, а оно, возможно, не поддерживается.

Наиболее близким для максимальной производительности является использование слоя с несколькими изображениями.

Phnghue
источник
Вы можете мне помочь? Ваше единственное решение, которое сработало в моем случае, поэтому я проголосовал за, есть небольшая проблема, которую нужно решить,
хамза хан
Я использую холст с держателем поверхности, поэтому я делаю это, чтобы очистить холст (холст взят из держателя поверхности): paint !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) холст !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (холст), чтобы иметь возможность его перерисовать: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) холст !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) теперь проблема после очистки холста, как это, когда я рисую растровое изображение поверх холста, оно рисуется после мигания цвета, что раздражает, вы можете показать мне путь сюда? @phnghue
hamza
@hamza khan Вы пробовали использовать SRC_OVER? Поскольку SRC_OVER по умолчанию является нормальным. Логика SRC перезаписывает старые данные пикселей, она заменяет альфа!
phnghue
0

Не забудьте вызвать invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
джаз чакраборти
источник
Зачем вам звонить invalidateпосле того, как что-то нарисовали?
RalfFriedl
0

Попробуйте удалить представление в onPause () действия и добавить onRestart ()

LayoutYouAddedYourView.addView (YourCustomView); LayoutYouAddedYourView.removeView (YourCustomView);

В тот момент, когда вы добавите свое представление, будет вызван метод onDraw ().

YourCustomView - это класс, расширяющий класс View.

Ришаб Джайн
источник
0

В моем случае я рисую свой холст в формате linearlayout. Чтобы очистить и снова перерисовать:

    LinearLayout linearLayout = findViewById(R.id.myCanvas);
    linearLayout.removeAllViews();

а затем я вызываю класс с новыми значениями:

    Lienzo fondo = new Lienzo(this,items);
    linearLayout.addView(fondo);

Это класс Лиенцо:

class Lienzo extends View {
    Paint paint;
    RectF contenedor;
    Path path;
    ArrayList<Items>elementos;

    public Lienzo(Context context,ArrayList<Items> elementos) {
        super(context);
        this.elementos=elementos;
        init();
    }

    private void init() {
        path=new Path();
        paint = new Paint();
        contenedor = new RectF();
        paint.setStyle(Paint.Style.FILL);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        contenedor.left = oneValue;
        contenedor.top = anotherValue;
        contenedor.right = anotherValue;
        contenedor.bottom = anotherValue;

        float angulo = -90; //starts drawing at 12 o'clock
        //total= sum of all element values
        for (int i=0;i<elementos.size();i++){
            if (elementos.get(i).angulo!=0 && elementos.get(i).visible){
                paint.setColor(elementos.get(i).backColor);
                canvas.drawArc(contenedor,angulo,(float)(elementos.get(i).value*360)/total,true,paint);

                angulo+=(float)(elementos.get(i).value*360)/total;
            }
        } //for example
    }
}
Карлос Гомес
источник
0

С помощью следующего подхода вы можете очистить холст целиком или только его часть.
Пожалуйста, не забудьте отключить аппаратное ускорение, поскольку PorterDuff.Mode.CLEAR не работает с аппаратным ускорением и, наконец, вызывается, setWillNotDraw(false)потому что мы переопределяем onDrawметод.

//view's constructor
setWillNotDraw(false);
setLayerType(LAYER_TYPE_SOFTWARE, null);

//view's onDraw
Paint TransparentPaint = new Paint();
TransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(0, 0, width, height, TransparentPaint); 
ucMedia
источник
это работа, но после этого мои рисунки больше не видны
Dyno Cris
1
@DynoCris Возможно, вы используете тот же объект Paint !? Попробуйте новый и не забудьте проголосовать, пожалуйста.
ucMedia
это мой код pastebin.com/GWBdtUP5, когда я снова пытаюсь рисовать, я не вижу линий более неожиданно. но холст ясен.
Dyno Cris
1
@DynoCris Пожалуйста, смените LAYER_TYPE_HARDWAREнаLAYER_TYPE_SOFTWARE
ucMedia
1
ах, правда, это моя ошибка. Но мне это все равно не помогло.
Dyno Cris
-1

Ваше первое требование, как очистить или перерисовать весь холст - Ответ - используйте метод canvas.drawColor (color.Black) для очистки экрана черным цветом или любым другим цветом, который вы укажете.

Ваше второе требование, как обновить часть экрана - Ответ - например, если вы хотите, чтобы все остальные вещи на экране оставались неизменными, но в небольшой области экрана отображалось целое число (например, счетчик), которое увеличивается каждые пять секунд. затем используйте метод canvas.drawrect, чтобы нарисовать эту небольшую область, указав левый верхний правый нижний и рисуя. затем вычислите значение своего счетчика (используя postdalayed в течение 5 секунд и т. д., как Handler.postDelayed (Runnable_Object, 5000);), преобразуйте его в текстовую строку, вычислите координаты x и y в этом маленьком прямоугольнике и используйте текстовое представление для отображения изменяющихся значение счетчика.

Чанду
источник
-1

Мне пришлось использовать отдельный проход рисования, чтобы очистить холст (заблокировать, нарисовать и разблокировать):

Canvas canvas = null;
try {
    canvas = holder.lockCanvas();
    if (canvas == null) {
        // exit drawing thread
        break;
    }
    canvas.drawColor(colorToClearFromCanvas, PorterDuff.Mode.CLEAR);
} finally {
    if (canvas != null) {
        holder.unlockCanvasAndPost(canvas);
    }
}
Фарид З
источник
-3

Просто позвони

canvas.drawColor (Color.TRANSPARENT)

Резник
источник
16
Это не сработает, потому что он просто нарисует прозрачность поверх текущего клипа ... фактически ничего не делая. Вы можете, однако, изменить режим передачи Porter-Duff для достижения желаемого эффекта: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Если я не ошибаюсь, цвет на самом деле может быть любым (не обязательно ПРОЗРАЧНЫМ), потому что PorterDuff.Mode.CLEARон просто очистит текущий клип (например, пробив дыру в холсте).
ashughes 05