Android、キャンバス:surfaceViewにあるキャンバス(=ビットマップ)をクリア(コンテンツを削除)するにはどうすればよいですか?


92

単純なゲームを作成するために、次のようなビットマップでキャンバスを描画するテンプレートを使用しました。

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

新しいレイアウト用にWHOLEキャンバスをクリア(または再描画)するにはどうすればよいですか(=ゲームで試してください)?

を呼び出すCanvas.drawColor(Color.BLACK)か、クリアしたい色を呼び出しますCanvas

そして、どうすれば画面の一部だけを更新できますか?

Android OSは画面の更新時にすべてのピクセルを再描画するため、「画面の一部」を更新するだけの方法はありません。ただし、で古い図面をクリアしない場合Canvas、古い図面はまだ表面にあり、これはおそらく画面の「一部のみを更新」する1つの方法です。

したがって、「画面の一部を更新する」場合は、Canvas.drawColor()メソッドの呼び出しを避けてください。


4
いいえ、サーフェス内のすべてのピクセルを描画しないと、非常に奇妙な結果が得られます(ポインターを交換することでダブルバッファリングが実現されるため、描画していない部分では、直前に何があったのかわかりません)。各反復でサーフェスのすべてのピクセルを再描画する必要があります。
Guillaume Brunerie

2
@ViktorLannér:を呼び出してmSurfaceHolder.unlockCanvasAndPost(c)からを呼び出すとc = mSurfaceHolder.lockCanvas(null)、新しいcものには以前のものと同じものが含まれませんc。OPが求めていたのは、SurfaceViewの一部だけを更新することはできないと思います。
Guillaume Brunerie

1
@Guillaume Brunerie:本当ですが、すべてではありません。私が書いたように、画面の一部を更新することはできません。ただし、画面上に古い図面を保持することはできます。これは、「画面の一部を更新する」という効果があります。サンプルアプリケーションで試してみてください。Canvas古い図面を保持します。
Wroclai、2011

2
シャム・オン・ミー!!! 私は配列を初期化しましたが、ゲームの開始時に一度だけで、連続した再起動ではありませんでした。そのため、古い部分はすべて「画面上」のままでした-新しく描画されただけです!!! 私は私の「ダム」のために非常に申し訳ありません!両方のおかげで!!!
samClem 2011

1
これはおそらく、ライブ壁紙が通常のSurfaceViewと同じように機能しないためです。とにかく、@ samClem:(ドキュメントに記載されているように)フレームごとにキャンバスのすべてのピクセルを常に再描画しないと、奇妙なちらつきが発生します。
Guillaume Brunerie

277

PorterDuffのクリアモードで透明色を描画すると、思い通りの仕上がりになります。

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

2
これは機能するはずですが、4.4(少なくともN7.2)ではバグがあるBitmap#eraseColor(Color.TRANSPARENT)ようです。
nmr 2014年

3
実際にColor.TRANSPARENTは、不要です。ビットマップにPorterDuff.Mode.CLEARは完全に十分ですARGB_8888。つまり、アルファとカラーを[0、0]に設定します。別の方法は、で使用Color.TRANSPARENTすることPorterDuff.Mode.SRCです。
jiasli 2014年

これは、透明ではなく空白の表面を残したままでは機能しません
pallav bohara

30

これはグーグルグループで見つけ、これは私のために働いた。

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

これは、ビットマップを設定したまま、長方形の描画などを削除します。


4
これは私に黒い領域を与えます-私がその後ろに持っているビットマップではありません:(
slott

クリアは問題ありませんが、ビットマップも削除されました。
Omar Rehman、

stackoverflow.com/questions/9691985/...キャンバスの一部の長方形にビットマップの四角形を描画する方法を説明します。長方形の画像を変更すると機能します。前のコンテンツを引き継いで、新しい画像を描画します
Martin

18

私は@mobistryの答えを試しました:

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

しかし、それは私にはうまくいきませんでした。

私にとっての解決策は:

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

たぶん、誰かが同じ問題を抱えています。


4
透明度を掛けることが答えです。そうしないと、一部のデバイスで黒色になることがあります。
Andreas Rudolph

16

Pathクラスのresetメソッドを使用する

Path.reset();

パスを使用している場合、これが本当に最良の答えです。他のものはユーザーに黒い画面を残すことができます。ありがとう。
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

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

2
これはどのようにして最も正しいのですか?単にdrawColorできるときにビットマップを使用するのはなぜですか?
RichieHH 2014

これは元のポスターでは機能しない可能性がありますが(ビットマップにアクセスできる必要はありません)、ビットマップが利用可能になったときにビットマップのコンテンツをクリアするのに非常に役立ちます。これらの場合、最初の行のみが必要です。便利なテクニック、+ 1
コードの先頭に立つ

コード全体を書き込めますか?
Dyno Cris

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に毎秒新しいビットマップを描画します。テストすると、ビットマップが常に同じバッファに書き込まれるわけではなく、画面が2つのバッファ間で切り替わることがわかります。

携帯電話(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の違いは、ビットマップがもう1つあることです。しかし、これは完全に間違っています。フレームnとフレームn + 2の間にビットマップがもう1つありますが、ビットマップを追加しただけでも、フレームnとフレームn + 1は完全に無関係です。
Guillaume Brunerie

いいえ、完全に理解しています。しかし、ご覧のとおり、コードは機能しません。しばらくすると、画面はアイコンでいっぱいになります。したがって、Canvas画面全体を完全に再描画する場合は、をクリアする必要があります。そのため、「以前の図面」についての私の説明は真実です。Canvas前の図面を保持します。
Wroclai、2011

4
はい、必要に応じて、Canvas以前の図面を保持します。しかし、これはまったく役に立ちlockCanvas()ません。問題は、使用する場合、それらの「以前の描画」が何であるかわからず、それらについて何も想定できないことです。おそらく、同じサイズのSurfaceViewを持つ2つのアクティビティがある場合、それらは同じCanvasを共有します。おそらく、ランダムなバイトが含まれている初期化されていないRAMのチャンクを取得します。おそらく、キャンバスには常にGoogleのロゴが書かれているのでしょう。わかりません。後にすべてのピクセルを描画しないアプリケーションはすべてlockCanvas(null)壊れます。
Guillaume Brunerie、2011

1
@GuillaumeBrunerie私があなたの投稿を見つけたという事実から2。5年後。Androidの公式ドキュメントは、「すべてのピクセルの描画」に関するアドバイスをサポートしています。キャンバスの一部が引き続きlockCanvas()を呼び出して存在することが保証されているケースは1つだけです。SurfaceHolderとその2つのlockCanvas()メソッドについては、Androidのドキュメントを参照してください。developer.android.com/reference/android/view/...developer.android.com/reference/android/view/...
UpLate

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


1

java androidのCanvasでの消去は、globalCompositeOperationを使用したJavaScriptによるHTML Canvasの消去と同様です。ロジックも同様でした。

UはDST_OUT(宛先出力)ロジックを選択します。

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

注:DST_OUTは、ペイントカラーのアルファが50%の場合、50%を消去できるため、より便利です。したがって、完全に透明にするには、色のアルファを100%にする必要があります。paint.setColor(Color.WHITE)を適用することをお勧めします。また、キャンバス画像の形式がRGBA_8888であることを確認してください。

消去後、SRC_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ロジックは古いピクセルデータを上書きし、アルファを置き換えます!
プンゲ


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

これは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); 

うまく
いきました

1
@DynoCrisおそらく同じペイントオブジェクトを使用しているのでしょう!?新しいものを試してみて、投票することを忘れないでください。
ucMedia

それは私のコードですpastebin.com/GWBdtUP5私はより多くのunfourtunatly行が表示されていない再描画しようとします。しかし、キャンバスは明確です。
Dyno Cris

@DynoCrisに変更LAYER_TYPE_HARDWAREしてくださいLAYER_TYPE_SOFTWARE
ucMedia

-1

最初の要件、キャンバス全体をクリアまたは再描画する方法-回答-canvas.drawColor(color.Black)メソッドを使用して、画面を黒または指定した色でクリアします。

2番目の要件、画面の一部を更新する方法-回答-たとえば、画面上の他のすべてのものを変更せずに、画面の小さな領域に表示して、5秒ごとに増加する整数(カウンターなど)を表示する場合。次に、canvas.drawrectメソッドを使用して、左上、右下、およびペイントを指定することにより、その小さな領域を描画します。次に、カウンター値を計算します(postdalayedを5秒間使用するなど、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、2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.