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 переосмислює кожен піксель під час оновлення екрана. Але коли ви не очищаєте старі креслення на своїх Canvas, старі креслення все ще виходять на поверхню, і це, мабуть, один із способів "оновити лише частину" екрана.

Отже, якщо ви хочете "оновити частину екрана", просто уникайте Canvas.drawColor()методу виклику .


4
Ні, якщо ви не намалюєте кожен піксель на поверхні, ви отримаєте дуже дивні результати (оскільки подвійна буферизація досягається шляхом обміну покажчиками, тому в тих частинах, де ви не малюєте, ви не побачите, що було безпосередньо раніше). Ви повинні перемальовувати кожен піксель поверхні на кожній ітерації.
Гійом Брунері,

2
@Viktor Lannér: Якщо ви телефонуєте, mSurfaceHolder.unlockCanvasAndPost(c)а потім c = mSurfaceHolder.lockCanvas(null), то новий cне містить того самого, що і попередній c. Ви не можете оновити лише частину SurfaceView, про що, напевно, просив ОП.
Гійом Брунері,

1
@Guillaume Brunerie: Правда, але не всі. Ви не можете оновити частину екрана, як я писав. Але ви можете зберігати на екрані старі малюнки, які матимуть ефект просто "оновити частину екрана". Спробуйте самі у зразку програми. Canvasзберігає старі малюнки.
Вроцлай,

2
МЕНІ ТАК СОРОМНО !!! Я ініціалізував свій масив ТІЛЬКИ РАЗ при запуску гри, НЕ під час послідовних перезапусків - отже ВСІ СТАРІ фігури залишились "на екрані" - їх просто намалювали заново !!! Я ДУЖЕ ЩАЛО за свою "німоту"! Дякую вам обом !!!
samClem

1
Тоді це, мабуть, тому, що живі шпалери працюють не так само, як звичайний SurfaceView. У будь-якому випадку, @samClem: завжди перемальовуйте кожен піксель полотна на кожному кадрі (як зазначено в документі), інакше у вас буде дивне мерехтіння.
Гійом Брунері,

278

Намалюйте прозорий колір за допомогою прозорого режиму PorterDuff робить фокус у тому, що я хотів.

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

2
Це має спрацювати, але, схоже, виправлена ​​помилка в 4.4 (принаймні на N7.2) Use Bitmap#eraseColor(Color.TRANSPARENT), як у відповіді HeMac нижче.
нмр

3
Насправді Color.TRANSPARENTце непотрібно. PorterDuff.Mode.CLEARцілком достатньо для ARGB_8888растрового зображення, що означає встановлення альфа та кольору на [0, 0]. Інший спосіб - використовувати Color.TRANSPARENTз PorterDuff.Mode.SRC.
jiasli

1
Це не працює для мене, залишаючи порожню поверхню замість прозорого
Паллав Бохара

31

Знайшов це в групах Google, і це мені вдалося ..

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

Це видаляє прямокутники малюнків тощо, зберігаючи при цьому встановлений растровий малюнок.


4
Це дає мені чорну область - а не растрове зображення, яке я маю за собою :(
слот

Це добре очищається, але растрове зображення також видаляється, на відміну від того, що ви сказали.
Омар Рехман,

у stackoverflow.com/questions/9691985/... пояснено, як намалювати прямокутник растрового зображення на якомусь прямокутнику полотна. Зміна зображення у прямокутнику таким чином працює: вивчаємо попередній вміст, малюємо нове зображення
Мартін,

18

Я спробував відповідь @mobistry:

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

Але у мене це не спрацювало.

Рішенням для мене було:

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

Можливо, у когось така сама проблема.


4
Помноження на прозорість - це відповідь. Інакше на деяких пристроях це може закінчитися чорним кольором.
Андреас Рудольф

17

використовувати метод скидання класу Path

Path.reset();

це дійсно найкраща відповідь, якщо ви використовуєте шлях. інші можуть залишити у користувача чорний екран. Дякую.
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

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

2
як це найбільш правильно? навіщо взагалі використовувати растрове зображення, коли ви можете просто намалюватиColor?
RichieHH

Хоча це може не спрацювати для оригінального плаката (вони не обов'язково мають доступ до растрової карти), це, безумовно, корисно для очищення вмісту растрової карти, коли це доступно. У цих випадках потрібно лише перший рядок. Корисна техніка, +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); Це працює для мене.
Aaron Lee

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.
Вроцлай,

1
Я думаю, ти не розумієш, що я маю на увазі. Що можна було очікувати, так це те, що різниця між фреймом n і фреймом n + 1 полягає в тому, що існує ще одна бітова карта. Але це абсолютно неправильно, існує ще одна бітова карта між фреймом n і фреймом n + 2 , але фрейми n і фрейми n + 1 абсолютно не пов'язані, навіть якщо я щойно додав растрову карту.
Гійом Брунері,

Ні, я вас повністю розумію. Але, як бачите, ваш код не працює. Через деякий час екран заповнився піктограмами. Тому нам потрібно очистити, Canvasякщо ми хочемо повністю перекроювати весь екран. Це робить моє твердження щодо "попередніх малюнків" істинним. CanvasЗберігає попередні малюнки.
Вроцлай,

4
Так, якщо ви хочете, a Canvasзберігає попередні креслення. Але це абсолютно марно, проблема полягає в тому, що коли ви використовуєте, lockCanvas()ви не знаєте, що таке "попередній малюнок", і не можете нічого про них припустити. Можливо, якщо є дві дії з SurfaceView однакового розміру, вони матимуть спільне полотно. Можливо, ви отримаєте шматок неініціалізованої оперативної пам'яті із випадковими байтами в ній. Можливо, у полотні завжди є логотип Google. Ви не можете знати. Будь-яка програма, яка не малює кожен піксель після, не lockCanvas(null)працює.
Гійом Брунері,

1
@GuillaumeBrunerie 2,5 роки після того, як я натрапив на ваш допис. Офіційна документація для Android підтримує ваші поради щодо "малювання кожного пікселя". Є лише один випадок, коли частина полотна гарантовано все ще буде там із подальшим викликом lockCanvas (). Зверніться до документації Android для SurfaceHolder та двох методів lockCanvas (). 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 - це схоже на стирання 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));

Оновлення дисплея невеликої області буквально знадобиться для доступу до графічного обладнання, і, можливо, воно не підтримується.

Найбільш близьким для найвищої продуктивності є використання багатошарового шару зображення.


Ви можете мені тут допомогти? Твоє єдине рішення, яке вийшло для моєї справи, тому я проголосував, є лише невелика проблема, з якою потрібно розібратися
хамза-хан,

Я використовую полотно з утримувачем поверхні, тому я роблю це, щоб очистити полотно (полотно, взяте з утримувача поверхні): 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 Ви пробували з SRC_OVER? Оскільки SRC_OVER за замовчуванням є нормальним. Логіка SRC перезаписує старі піксельні дані, вона замінює альфа-версію!
phnghue


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_HARDWAREнаLAYER_TYPE_SOFTWARE
ucMedia

1
о, справді, це моя помилка. Але це все одно мені не допомогло до кінця.
Dyno Cris

-1

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

Ваша друга вимога, як оновити частину екрана - відповідь - наприклад, якщо ви хочете, щоб усі інші речі залишалися незмінними на екрані, але на невеликій ділянці екрана відображали ціле число (скажімо, лічильник), яке збільшується через кожні п’ять секунд. потім використовуйте метод 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.