Android, 캔버스 : surfaceView에있는 캔버스 (= 비트 맵)를 지우는 (내용을 삭제) 어떻게합니까?


93

간단한 게임을 만들기 위해 다음과 같은 비트 맵으로 캔버스를 그리는 템플릿을 사용했습니다.

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

답변:


77

새 레이아웃을 위해 전체 캔버스를 지우거나 다시 그리려면 어떻게합니까 (= 게임에서 시도)?

을 호출 Canvas.drawColor(Color.BLACK)하거나 지우고 싶은 색상을 선택하세요 Canvas.

그리고 : 화면의 일부만 업데이트하려면 어떻게해야합니까?

Android OS는 화면을 업데이트 할 때 모든 픽셀을 다시 그리기 때문에 "화면의 일부"만 업데이트하는 방법은 없습니다. 그러나에서 이전 그림을 지우지 않을 때 Canvas이전 그림은 여전히 ​​표면에 남아 있으며 이는 아마도 화면의 "일부만 업데이트"하는 한 가지 방법 일 것입니다.

따라서 "화면의 일부를 업데이트"하려면 Canvas.drawColor()메서드 호출을 피하십시오 .


4
아니요, 표면의 모든 픽셀을 그리지 않으면 매우 이상한 결과가 나타납니다 (포인터를 교체하여 이중 버퍼링이 이루어 지므로 그리지 않는 부분에서는 바로 이전에 무엇이 있었는지 볼 수 없습니다). 당신 각 반복에서 표면의 모든 픽셀을 다시 그려야.
Guillaume Brunerie 2011

2
@Viktor Lannér :를 호출 mSurfaceHolder.unlockCanvasAndPost(c)한 다음 을 호출 c = mSurfaceHolder.lockCanvas(null)하면 새 c에는 이전 c. OP에서 요청한 SurfaceView의 일부만 업데이트 할 수는 없습니다.
Guillaume Brunerie 2011

1
@Guillaume Brunerie : 사실이지만 전부는 아닙니다. 내가 쓴 것처럼 화면의 일부를 업데이트 할 수 없습니다. 그러나 화면에 오래된 그림을 유지하면 "화면의 일부만 업데이트"하는 효과가 있습니다. 샘플 애플리케이션에서 직접 시도해보십시오. Canvas오래된 그림을 유지합니다.
Wroclai 2011

2
부끄러워! 나는 연속적으로 다시 시작할 때가 아니라 게임을 시작할 때 한 번만 배열을 초기화했습니다. 따라서 모든 오래된 조각은 "화면"에 남아 있습니다-방금 새로 그려졌습니다 !!! 나는 나의 "벙어리"에 대해 매우 미안하다! 둘 다 감사합니다 !!!
samClem 2011

1
그렇다면 라이브 월페이퍼가 일반 SurfaceView와 동일한 방식으로 작동하지 않기 때문일 수 있습니다. 어쨌든 @samClem : 항상 각 프레임 (문서에 명시된대로)에서 Canvas의 모든 픽셀을 다시 그리지 않으면 이상한 깜박임이 발생합니다.
Guillaume Brunerie 2011

278

PorterDuff 클리어 모드로 투명한 색상을 그립니다.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

2
이것은 작동하지만 Bitmap#eraseColor(Color.TRANSPARENT)아래 HeMac의 답변에서와 같이 4.4 (적어도 N7.2에서는) Use 에서 버그가있는 것으로 보입니다.
nmr

3
사실 Color.TRANSPARENT불필요합니다. 알파와 색상을 [0, 0]으로 설정하는 것을 의미 PorterDuff.Mode.CLEAR하는 ARGB_8888비트 맵에 충분합니다 . 또 다른 방법은 사용하는 것입니다 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

잘 지워지지 만 비트 맵도 당신이 말한 것처럼 제거됩니다.
Omar Rehman 2015 년

stackoverflow.com/questions/9691985/... 캔버스의 일부 사각형에 비트 맵의 사각형을 그리는 방법을 설명합니다. 직사각형의 이미지를 변경하면 작동합니다. 이전 내용을
Martin

18

@mobistry의 답변을 시도했습니다.

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

그러나 그것은 나를 위해 일하지 않았습니다.

저에게 해결책은 다음과 같습니다.

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

아마도 어떤 사람은 같은 문제를 가지고있을 것입니다.


4
투명성을 곱하는 것이 답입니다. 그렇지 않으면 일부 장치에서 검정색이 될 수 있습니다.
Andreas Rudolph

17

경로 클래스의 재설정 방법 사용

Path.reset();

경로를 사용하는 경우 이것이 실제로 가장 좋은 대답입니다. 다른 것들은 사용자에게 검은 화면을 남길 수 있습니다. 감사.
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

2
이것이 가장 정확한 방법은 무엇입니까? drawColor 만 할 수 있는데 왜 비트 맵을 사용합니까?
RichieHH 2014-08-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

다음은 각 프레임에서 항상 Canvas의 모든 픽셀을 다시 그려야 함을 보여주는 최소한의 예제 코드입니다.

이 활동은 이전에 화면을 지우지 않고 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/# 1 초처럼 지연을 추가하면 이중 버퍼링이 더 눈에 띄지 만 화면에 여전히 이전 그림이 있습니다. 또한 코드가 잘못되었습니다. SurfaceHolder.Callback단지 Callback.
Wroclai 2011

1
무슨 말인지 이해 못하시는 것 같아요. 예상 할 수있는 것은 프레임 n 과 프레임 n + 1 의 차이점은 비트 맵이 하나 더 있다는 것입니다. 그러나 이것은 완전히 잘못된 것입니다. 프레임 n 과 프레임 n + 2 사이에 하나 이상의 비트 맵이 있지만, 방금 비트 맵을 추가하더라도 프레임 n 과 프레임 n + 1 은 완전히 관련이 없습니다.
Guillaume Brunerie 2011

아니, 나는 당신을 완전히 이해합니다. 그러나 보시다시피 코드가 작동하지 않습니다. 잠시 후 화면이 아이콘으로 가득 차게됩니다. 따라서 Canvas전체 화면을 완전히 다시 그리려면 선택 을 취소해야합니다 . 그것은 "이전 그림"에 대한 나의 진술을 사실로 만든다. 는 Canvas이전의 도면을 유지합니다.
Wroclai 2011

4
예, 원하는 경우 Canvas이전 도면을 유지합니다. 그러나 이것은 완전히 쓸모가 없습니다. 문제는 사용할 때 lockCanvas()그“이전 그림”이 무엇인지 모르고 그것에 대해 아무것도 가정 할 수 없다는 것입니다. 동일한 크기의 SurfaceView를 사용하는 두 개의 활동이있는 경우 동일한 Canvas를 공유 할 수 있습니다. 아마도 임의의 바이트가있는 초기화되지 않은 RAM 덩어리를 얻을 수 있습니다. 아마도 Canvas에는 항상 Google 로고가 쓰여있을 것입니다. 당신은 알 수 없습니다. 이후 모든 픽셀을 그리지 않는 응용 프로그램 lockCanvas(null)은 손상됩니다.
Guillaume Brunerie 2011

1
@GuillaumeBrunerie 내가 귀하의 게시물을 발견 한 사실 2.5 년 후. 공식 Android 문서는 "모든 픽셀 그리기"에 관한 조언을 지원합니다. lockCanvas ()에 대한 후속 호출을 통해 캔버스의 일부가 여전히 존재하는 경우가 하나뿐입니다. SurfaceHolder 및 두 개의 lockCanvas () 메서드에 대한 Android 설명서를 참조하세요. developer.android.com/reference/android/view/…developer.android.com/reference/android/view/…
UpLate 2011

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
코드가 문제를 해결하는 방법에 대한 설명을 추가하십시오. 이는 낮은 품질의 포스트로 플래그가 - 검토에서
SURAJ 라오을

1

Java android의 Canvas에서 지우기는 globalCompositeOperation을 사용하여 javascript를 통해 HTML Canvas를 지우는 것과 유사합니다. 논리는 비슷했습니다.

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

작은 영역 디스플레이 업데이트는 말 그대로 그래픽 하드웨어에 액세스해야하며 지원되지 않을 수 있습니다.

최고의 성능을위한 가장 가까운 방법은 다중 이미지 레이어를 사용하는 것입니다.


여기서 도와 주실 수 있나요? 귀하의 솔루션이 제 사건에 대해 해결 된 유일한 솔루션이므로 제가 투표를했습니다. 해결해야 할 작은 문제가 있습니다
hamza khan

표면 홀더가있는 캔버스를 사용하고 있으므로 캔버스 (표면 홀더에서 가져온 캔버스) : paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. drawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (canvas) 그러면 다시 그릴 수 있습니다. paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) 이제 문제가됩니다. 이렇게 캔버스를 지우고 나서 캔버스 위에 비트 맵을 그리면 색이 깜빡이면서 그려집니다. 귀찮습니다. 여기서 길을 보여줄 수 있나요? @phnghue
hamza khan

@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 = findViewById(R.id.myCanvas);
    linearLayout.removeAllViews();

그런 다음 새 값으로 클래스를 호출합니다.

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

이것은 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
    }
}

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

그것은 작동하지만 그 후 내 그림은 더 이상 보이지 않습니다
Dyno Cris

1
@DynoCris 아마도 동일한 Paint 개체를 사용하고 계십니까!? 새로운 것으로 시도하고 제발 찬성하는 것을 잊지 마십시오.
ucMedia

다시 그리려고 할 때 내 코드 pastebin.com/GWBdtUP5 더 불행하게도 선이 보이지 않습니다. 하지만 캔 스바는 깨끗합니다.
Dyno Cris

1
제발 변화를 @DynoCris LAYER_TYPE_HARDWARELAYER_TYPE_SOFTWARE
ucMedia

1
오, 정말 내 실수 야. 그러나 그것은 불행히도 어쨌든 나를 돕지 못했습니다.
Dyno Cris

-1

첫 번째 요구 사항, 전체 캔버스를 지우거나 다시 그리는 방법-답변-canvas.drawColor (color.Black) 메서드를 사용하여 검정색 또는 지정한 색상으로 화면을 지우십시오.

두 번째 요구 사항, 화면 일부를 업데이트하는 방법-답변-예를 들어 화면의 다른 모든 것을 변경하지 않고 화면의 작은 영역에 5 초마다 증가하는 정수 (예 : 카운터)를 표시하려는 경우. 그런 다음 canvas.drawrect 메서드를 사용하여 왼쪽 상단 오른쪽 하단 및 페인트를 지정하여 작은 영역을 그립니다. 그런 다음 카운터 값을 계산하고 (postdalayed for 5 seconds etc., llike 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 2011 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.