Android, kanwa: Jak wyczyścić (usunąć zawartość) kanwy (= mapy bitowe), żyjąc w SurfaceView?

96

Aby stworzyć prostą grę, użyłem szablonu, który rysuje płótno z bitmapami w następujący sposób:

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); } }

(Kanwa jest zdefiniowana w „run ()” / SurfaceView znajduje się w GameThread).

Moje pierwsze pytanie brzmi: jak wyczyścić (lub przerysować) całe płótno dla nowego układu?
Po drugie, jak mogę zaktualizować tylko część ekranu?

// 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
źródło

Odpowiedzi:

78

Jak wyczyścić (lub przerysować) CAŁĄ kanwę dla nowego układu (= spróbuj w grze)?

Po prostu zadzwoń Canvas.drawColor(Color.BLACK)lub jakimkolwiek kolorem, który chcesz usunąć Canvas.

I: jak mogę zaktualizować tylko część ekranu?

Nie ma takiej metody, która po prostu aktualizuje „część ekranu”, ponieważ system operacyjny Android przerysowuje każdy piksel podczas aktualizacji ekranu. Ale jeśli nie usuwasz starych rysunków na swoim komputerze Canvas, stare rysunki wciąż są na powierzchni i jest to prawdopodobnie jeden ze sposobów „zaktualizowania tylko części” ekranu.

Tak więc, jeśli chcesz „zaktualizować część ekranu”, po prostu unikaj wywoływania Canvas.drawColor()metody.

Wroclai
źródło
4
Nie, jeśli nie narysujesz każdego piksela na powierzchni, uzyskasz bardzo dziwne wyniki (ponieważ podwójne buforowanie uzyskuje się przez zamianę wskaźników, więc w częściach, w których nie rysujesz, nie zobaczysz, co było przedtem). Państwo mają do ponownego każdy piksel powierzchni przy każdej iteracji.
Guillaume Brunerie
2
@Viktor Lannér: Jeśli zadzwonisz, mSurfaceHolder.unlockCanvasAndPost(c)a potem c = mSurfaceHolder.lockCanvas(null), to nowy cnie zawiera tego samego, co poprzedni c. Nie można zaktualizować tylko części SurfaceView, o co chyba pytał OP.
Guillaume Brunerie
1
@Guillaume Brunerie: Prawda, ale nie wszystko. Nie możesz zaktualizować części ekranu, jak napisałem. Ale możesz zachować stare rysunki na ekranie, co spowoduje po prostu „zaktualizowanie części ekranu”. Wypróbuj sam w przykładowej aplikacji. Canvaszachowuje stare rysunki.
Wroclai
2
WSTYD NA MNIE !!! Zainicjowałem swoją tablicę TYLKO RAZ na początku gry, NIE przy kolejnych ponownych uruchomieniach - więc WSZYSTKIE STARE elementy pozostały "na ekranie" - zostały po prostu narysowane od nowa !!! BARDZO PRZEPRASZAM za moją „głupotę”! Dziękuję wam obojgu !!!
samClem
1
Dzieje się tak prawdopodobnie dlatego, że tapety na żywo nie działają tak samo, jak zwykły SurfaceView. W każdym razie, @samClem: zawsze przerysuj każdy piksel kanwy w każdej klatce (zgodnie z opisem w dokumencie) lub będziesz mieć dziwne migotanie.
Guillaume Brunerie
280

Rysowanie przezroczystego koloru za pomocą trybu czyszczenia PorterDuff załatwia to, co chciałem.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
źródło
2
Powinno to działać, ale wydaje się, że zawiera błąd w 4.4 (przynajmniej na N7.2). Użyj Bitmap#eraseColor(Color.TRANSPARENT), jak w odpowiedzi HeMaca poniżej.
NMR
4
W rzeczywistości Color.TRANSPARENTjest niepotrzebne. PorterDuff.Mode.CLEARjest w zupełności wystarczający dla ARGB_8888mapy bitowej, co oznacza ustawienie alfa i koloru na [0, 0]. Innym sposobem jest użycie Color.TRANSPARENTz PorterDuff.Mode.SRC.
jiasli
1
U mnie to nie działa, zostawiając pustą powierzchnię zamiast Przezroczystego
pallav bohara
31

Znalazłem to w grupach Google i to zadziałało.

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

Spowoduje to usunięcie prostokątów rysunków itp. Przy zachowaniu ustawionej mapy bitowej.

Rukmal Dias
źródło
4
To daje mi czarny obszar - a nie bitmapę, którą mam za nim :(
slott
Czyści się dobrze, ale mapa bitowa jest również usuwana, w przeciwieństwie do tego, co powiedziałeś.
Omar Rehman
na stackoverflow.com/questions/9691985/ ... wyjaśniono, jak narysować prostokąt bitmapy na jakimś prostokącie płótna. Zmiana obrazu w prostokącie działa w ten sposób: poznaj poprzednią zawartość, narysuj nowy obraz
Martin
18

użyj metody resetowania klasy Path

Path.reset();
Rakesh
źródło
to naprawdę najlepsza odpowiedź, jeśli używasz ścieżki. pozostałe mogą pozostawić użytkownika z czarnym ekranem. dzięki.
j2emanue
18

Wypróbowałem odpowiedź @mobistry:

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

Ale to nie zadziałało.

Rozwiązaniem dla mnie było:

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

Może ktoś ma ten sam problem.

Derzu
źródło
4
Odpowiedzią jest pomnożenie przez przejrzystość. W przeciwnym razie na niektórych urządzeniach może mieć czarny kolor.
Andreas Rudolph
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
źródło
2
jak to jest najbardziej poprawne? po co w ogóle używać bitmapy, skoro można po prostu drawColor?
RichieHH
Chociaż może to nie działać w przypadku oryginalnego plakatu (niekoniecznie mają oni dostęp do mapy bitowej), jest to zdecydowanie przydatne do czyszczenia zawartości mapy bitowej, gdy jest ona dostępna. W takich przypadkach wymagana jest tylko pierwsza linia. Przydatna technika, +1
głowa w kodach
4

proszę wkleić poniższy kod do konstruktora klasy rozszerzającej surfaceview .............

kodowanie konstruktorów

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

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

kodowanie 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" />

wypróbuj powyższy kod ...

Hemant Chand Dungriyal
źródło
holder.setFormat (PixelFormat.TRANSPARENT); Mi to pasuje.
Aaron Lee
3

Oto kod minimalnego przykładu pokazującego, że zawsze musisz przerysować każdy piksel kanwy w każdej klatce.

To działanie rysuje nową mapę bitową co sekundę na SurfaceView, bez wcześniejszego czyszczenia ekranu. Jeśli to przetestujesz, zobaczysz, że mapa bitowa nie zawsze jest zapisywana w tym samym buforze, a ekran będzie przełączał się między dwoma buforami.

Przetestowałem to na swoim telefonie (Nexus S, Android 2.3.3) oraz na emulatorze (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();
                }
            }
        }   
    }
}
Guillaume Brunerie
źródło
Cóż, wygląda na to, że się mylisz, spójrz na mój zrzut ekranu: img12.imageshack.us/i/devicey.png/# Kiedy dodasz opóźnienie, takie jak jedna sekunda, podwójne buforowanie jest bardziej zauważalne, ale (!) na ekranie wciąż są poprzednie rysunki. Ponadto twój kod jest nieprawidłowy: powinien być SurfaceHolder.Callback, a nie tylko Callback.
Wroclai
1
Myślę, że nie rozumiesz, o co mi chodzi. Można by się spodziewać, że różnica między ramką n i ramką n + 1 polega na tym, że jest jeszcze jedna bitmapa. Ale jest to całkowicie błędne, jest jeszcze jedna mapa bitowa między ramką n i ramką n + 2 , ale ramka n i ramka n + 1 są całkowicie niezwiązane, nawet jeśli właśnie dodałem bitmapę.
Guillaume Brunerie
Nie, w pełni cię rozumiem. Ale, jak widzisz, twój kod nie działa. Po chwili ekran jest pełen ikon. Dlatego musimy wyczyścić, Canvasjeśli chcemy w pełni przerysować cały ekran. To sprawia, że ​​moje stwierdzenie o „poprzednich rysunkach” jest prawdziwe. CanvasUtrzymuje wcześniejsze rysunki.
Wroclai
4
Tak, jeśli chcesz, Canvaszachowuje poprzednie rysunki. Ale to jest kompletnie bezużyteczne, problem w tym, że kiedy używasz lockCanvas(), nie wiesz, jakie są te „poprzednie rysunki” i nie możesz nic o nich założyć. Być może, jeśli istnieją dwa działania z SurfaceView o tym samym rozmiarze, będą one współużytkować tę samą kanwę. Być może dostaniesz kawałek niezainicjowanej pamięci RAM z losowymi bajtami. Być może, że zawsze na płótnie jest wypisane logo Google. Nie możesz wiedzieć. Każda aplikacja, która nie rysuje po każdym pikselu, lockCanvas(null)jest zepsuta.
Guillaume Brunerie
1
@GuillaumeBrunerie 2,5 roku po tym, jak natknąłem się na Twój post. Oficjalna dokumentacja systemu Android zawiera porady dotyczące „rysowania każdego piksela”. Jest tylko jeden przypadek, w którym część płótna jest gwarantowana, a kolejne wywołanie lockCanvas (). Zapoznaj się z dokumentacją systemu Android dotyczącą SurfaceHolder i jego dwóch metod lockCanvas (). developer.android.com/reference/android/view/… i developer.android.com/reference/android/view/ ...
UpLate
3

Dla mnie dzwonienie Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)lub coś podobnego działałoby dopiero po dotknięciu ekranu. Więc nazwałbym powyższą linię kodu, ale ekran byłby jasny dopiero po tym, jak dotknąłem ekranu. Więc to, co zadziałało, to wywołanie, invalidate()a następnie, init()które jest wywoływane w momencie tworzenia, aby zainicjować widok.

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();
}
Jason Crosby
źródło
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
aliakbarian
źródło
2
Dodaj wyjaśnienie, w jaki sposób Twój kod rozwiązuje problem. To jest oflagowane jako post niskiej jakości - Z recenzji
Suraj Rao
1

Wymazywanie na kanwie w java Android jest podobne do kasowania HTML Canvas za pomocą javascript z globalCompositeOperation. Logika była podobna.

U wybierze logikę DST_OUT (Destination Out).

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

Uwaga: DST_OUT jest bardziej przydatne, ponieważ może wymazać 50%, jeśli kolor farby ma 50% alfa. Tak więc, aby całkowicie przejść do przezroczystości, alfa koloru musi wynosić 100%. Zaleca się zastosowanie paint.setColor (Color.WHITE). I upewnij się, że format obrazu płótna to RGBA_8888.

Po wymazaniu wróć do normalnego rysowania za pomocą SRC_OVER (Source Over).

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

Aktualizacja małego obszaru wyświetlania będzie wymagała dosłownie dostępu do sprzętu graficznego i może nie być obsługiwana.

Najbardziej zbliżona do najwyższej wydajności jest użycie wielu warstw obrazu.

phnghue
źródło
Możesz mi tu pomóc? Twoje jest jedynym rozwiązaniem, które sprawdziło się w mojej sprawie, więc zagłosowałem za mną, jest tylko mały problem, który należy rozwiązać
hamza khan
Używam płótna z uchwytem powierzchniowym, więc robię to, aby wyczyścić płótno (płótno pobrane z uchwytu powierzchni): farba !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) płótno !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (płótno), aby móc go przerysować: malować !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) teraz problem czy po wyczyszczeniu płótna w ten sposób, kiedy rysuję bitmapę na płótnie, jest ona rysowana po mrugnięciu Color, co jest denerwujące, czy możesz mi pokazać drogę tutaj? @phnghue
khan
@hamza khan Czy próbowałeś z SRC_OVER? Ponieważ SRC_OVER jest domyślnie normalne. Logika SRC zastępuje stare dane pikseli, zastępuje alfa!
phnghue
0

Nie zapomnij wywołać invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
jazz chakraborty
źródło
Dlaczego miałbyś dzwonić invalidatepo narysowaniu czegoś?
RalfFriedl
0

Spróbuj usunąć widok przy onPause () działania i dodaj onRestart ()

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

W momencie dodania widoku zostanie wywołana metoda onDraw ().

YourCustomView, to klasa, która rozszerza klasę View.

Rishabh Jain
źródło
0

W moim przypadku rysuję płótno w linearnym układzie. Aby ponownie wyczyścić i przerysować:

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

a następnie dzwonię do klasy z nowymi wartościami:

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

To jest klasa Lienzo:

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
    }
}
Carlos Gómez
źródło
0

W następujący sposób możesz wyczyścić całe płótno lub tylko jego część.
Nie zapomnij wyłączyć akceleracji sprzętowej, ponieważ PorterDuff.Mode.CLEAR nie działa z akceleracją sprzętową i na koniec wywołaj, setWillNotDraw(false)ponieważ nadpisujemy onDrawmetodę.

//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
źródło
to działa, ale potem moje losowania już nie są widoczne
Dyno Cris
1
@DynoCris Być może używasz tego samego obiektu Paint !? Spróbuj z nowym i nie zapomnij o głosowaniu za.
ucMedia
to mój kod pastebin.com/GWBdtUP5. Kiedy próbuję narysować ponownie, nie widzę linii bardziej niefortunnie. ale cansvas jest jasne.
Dyno Cris
1
@DynoCris Zmień LAYER_TYPE_HARDWAREnaLAYER_TYPE_SOFTWARE
ucMedia
1
och, naprawdę, to mój błąd. Ale i tak nie pomogło mi to na szczęście.
Kryzys Dyno
-1

Twoje pierwsze wymaganie, jak wyczyścić lub przerysować całe płótno - Odpowiedź - użyj metody canvas.drawColor (color.Black), aby wyczyścić ekran kolorem czarnym lub jakimkolwiek innym.

Twoje drugie wymaganie, jak zaktualizować część ekranu - Odpowiedź - na przykład, jeśli chcesz, aby wszystkie inne rzeczy na ekranie pozostały niezmienione, ale w małym obszarze ekranu, aby wyświetlić liczbę całkowitą (powiedzmy licznik), która rośnie co pięć sekund. następnie użyj metody canvas.drawrect, aby narysować ten mały obszar, określając lewy górny prawy dół i malując. następnie oblicz wartość swojego licznika (używając postdalayed przez 5 sekund itp., l jak Handler.postDelayed (Runnable_Object, 5000);), przekonwertuj go na ciąg tekstowy, oblicz współrzędne xiy w tym małym prostokącie i użyj widoku tekstu, aby wyświetlić zmieniające się wartość licznika.

Chandu
źródło
-1

Musiałem użyć osobnego przebiegu rysowania, aby wyczyścić płótno (zablokować, narysować i odblokować):

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);
    }
}
Farid Z
źródło
-3

Zadzwoń

canvas.drawColor (Color.TRANSPARENT)

reznic
źródło
16
To nie działa, ponieważ po prostu rysuje przezroczystość na bieżącym klipie ... skutecznie nic nie robiąc. Można jednak zmienić tryb transferu Porter-Duff, aby osiągnąć pożądany efekt: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Jeśli się nie mylę, kolor może być właściwie dowolny (nie musi być PRZEZROCZYSTY), ponieważ PorterDuff.Mode.CLEARpo prostu wyczyści bieżący klip (jak przebicie dziury w płótnie).
ashughes,