Laura Lemay
A primeira coisa que alguma vez vi Java fazer foi uma animação: um grande Hi there! vermelho que encontrou por acaso a tela do direito ao esquerdo. Mesmo aquela forma simples da animação foi bastante para fazer-me parar e pensar, "isto é realmente fresco".
Aquele tipo da animação simples toma só alguns métodos para implementar em Java, mas aqueles poucos métodos são a base de qualquer Java applet que quer atualizar a tela dinamicamente - para algo tão simples como animação flamejante applets, ou para applets mais complexo que precisaria de atualizar-se baseado em dados aos quais vêm do usuário, de bancos de dados unidos sobre a rede, ou de qualquer outra fonte.
A animação em Java realiza-se por várias partes relacionadas do Resumo de Java Caixa de ferramentas de Windowing (awt). Hoje aprenderá o fundamentals da animação em Java: como várias partes do sistema todos colaboram para que possa criar números móveis e applets dinamicamente updatable. Especificamente, explorará o seguinte:
Em todas as partes hoje, também trabalhará com muitos exemplos de verdadeiros applets que criam a animação ou executam uma espécie de movimento dinâmico.
A animação em Java implica dois passos básicos: construção de uma armação de animação, e logo pergunta de Java pintar aquela armação. Repete estes passos segundo a necessidade para criar a ilusão do movimento. O applets gráfico básico, estático que criou ontem ensinou-lhe como realizar a primeira parte; tudo isto deixa-se é como dizer a Java pintar uma armação.
O método de paint(), como aprendeu ontem, chama-se sempre que um applet tenha de pintar-se - quando o applet se desenha inicialmente, quando a janela que o contém se move, ou quando outra janela se move de sobre ele. Também pode, contudo, pedir que Java repinte o applet de uma vez seleciona. Deste modo, para modificar a aparência do que está na tela, constrói a imagem ou "enquadra-o" querem pintar, e logo pedir que Java pinte esta armação. Se fizer isto repetidamente, e bastante rápido, adquire a animação dentro do seu Java applet. Isto é tudo que há para ele.
Onde faz tudo isso realiza-se? Não no próprio método de paint(). Todo o paint() faz põe-se pontos na tela. paint(), em outras palavras, é só responsável pela armação atual da animação. O verdadeiro trabalho da modificação o que paint() faz, de modificar a armação de uma animação, de fato ocorre em outro lugar na definição do seu applet.
Nisto "em outro lugar", constrói a armação (variáveis de jogo de paint() para usar, para criar Color ou Font ou outros objetos de que paint() precisará), e logo chamam o método de repaint(). repaint() é o gatilho que faz que a Java chame paint() e faz que à sua armação se desenhe.
Nota técnica |
Como Java applet pode conter muitos componentes diferentes que toda a necessidade a pintar-se (como aprenderá depois nesta semana), e de fato, applets pode ser introduzida dentro de uma aplicação de Java maior que também pinta à tela de modos semelhantes, quando chama repaint() (e por isso paint()) não desenha de fato imediatamente à tela como faz em outra janela ou caixas de ferramentas de gráficos. Em vez disso, repaint() é um pedido em Java para repintar o seu applet logo que possa. Também, se demasiados pedidos de repaint() se fazem em um período de tempo curto, o sistema só pode chamar repaint() uma vez para todos eles. A maior parte do tempo, o atraso entre a chamada e o repinte real é insignificante. Contudo, para laços muito apertados, o awt pode cair várias chamadas a repaint() em um. Lembre-se disto como cria a sua própria animação. |
Lembre-se de start() e stop() do Dia 8, "Java Fundamentos de Applet"? Estes são os métodos que provocam o seu applet para começar e deixar de correr. Não usou start() e stop() ontem porque o applets naquele dia não fez nada exceto a pintura uma vez. Com a animação e outro Java applets que processam de fato e correndo dentro de algum tempo, precisará de utilizar start() e stop() para provocar a partida da execução do seu applet e pará-lo de correr quando deixa a página que contém isto applet. Para muitos applets, quererá ignorar start() e stop() por somente esta razão.
O método de start() provoca a execução do applet. Pode fazer o trabalho de todo o applet dentro daquele método, ou pode chamar métodos de outro objeto para fazer assim. Normalmente, start() usa-se para criar e começar a execução de um fio portanto o applet pode correr no seu próprio tempo.
stop(), de outro lado, suspende a execução de um applet portanto quando parte a página na qual o applet expõe, não continua correndo e esgotar recursos de sistema. A maior parte do tempo quando cria um método de start(), também deve criar um stop() correspondente.
Há uma mais parte à mistura de animação sobre que terá de saber, e isto é fios. Vou discutir fios no detalhe muito maior mais tarde nesta lição (e em até mais detalhe do Dia 18, "Multienfiando") mas por agora aqui está a ideia básica: Algo que faz em um programa Java que corre constantemente e toma muito tempo de processamento deve correr no seu próprio fio. A animação é uma destas coisas. Para realizar a animação em Java, por isso, usa o método de start() para começar um fio, e logo fazer todo o seu processamento de animação dentro do método de run() do fio. Isto permite à animação correr sozinho sem mexer em qualquer outra parte do programa.
Explicando como fazer a animação de Java é mais de uma tarefa do que mostrar-lhe de fato como trabalha no código. Um exemplo ajudará a fazer a relação entre todos estes métodos mais clara.
A listagem 10.1 mostra uma amostra applet que usa técnicas de animação applet básicas para expor a data e tempo e constantemente a atualiza cada segundo, criando um relógio digital animado muito simples (uma armação daquele relógio mostra-se na Figura 10.1).
A figura 10.1: O relógio digital.
Este applet usa o paint(), repaint(), start() e métodos de stop(). Também usa fios. Para esta discussão, vamos nos concentrar nas partes de animação do applet e não nos incomodaremos tanto com como os fios trabalham. Tomaremos outra olhada neste applet depois, depois de ter discutido fios no maior detalhe.
A listagem 10.1. O DigitalClock applet.
1: import java.awt.Graphics; 2: import java.awt.Font; 3: import java.util.Date; 4: 5: public class DigitalClock extends java.applet.Applet 6: implements Runnable { 7: 8: Font theFont = new Font("TimesRoman",Font.BOLD,24); 9: Date theDate; 10: Thread runner; 11: 12: public void start() { 13: if (runner == null) { 14: runner = new Thread(this); 15: runner.start(); 16: } 17: } 18: 19: public void stop() { 20: if (runner != null) { 21: runner.stop(); 21: runner = null; 22: } 23: } 24: 25: public void run() { 26: while (true) { 27: theDate = new Date(); 28: repaint(); 29: try { Thread.sleep(1000); } 30: catch (InterruptedException e) { } 31: } 32: } 33: 34: public void paint(Graphics g) { 35: g.setFont(theFont); 36: g.drawString(theDate.toString(),10,50); 37: } 38:}
Análise |
Olharemos para este applet da perspectiva das partes de animação reais nesta seção e trataremos com as partes que dirigem fios mais tarde. |
As linhas 7 e 8 definem duas variáveis de exemplo básicas: theFont e theDate, que mantêm objetos que representam a fonte atual e a data atual, respectivamente. Aprenderá mais sobre estes depois.
O start() e os métodos de stop() aqui começam e param um fio; a maior parte do trabalho do applet continua nos métodos de run() (linhas 25 para 32).
Dentro de run() é onde a animação de fato se realiza. Observe o laço de while dentro deste método (linha 26); considerando que o teste (true) sempre devolve true, o laço nunca saídas. Uma armação de animação única constrói-se dentro que laço de while, com os seguintes passos:
No método de paint() em linhas 34 para 37. Aqui, dentro de paint(), tudo que acontece é que a fonte atual (no theFont variável) se estabelece, e a própria data imprime-se para a tela (observe que tem de chamar o método de toString() para converter a data em uma cadeia). Como paint() se chama repetidamente com qualquer valor resulta estar em theDate, a cadeia atualiza-se cada segundo para refletir a nova data.
Há algumas coisas a observar sobre este exemplo. Em primeiro lugar, poderia pensar que seria mais fácil criar o novo objeto de Date dentro do método de paint(). Daqueles caminho pode usar uma variável local e não precisar de uma variável de exemplo para passar o objeto de Date em volta. Embora fazendo coisas aquele caminho cria o código mais limpo, também resulta em um programa menos eficiente. O método de paint() chama-se cada vez quando uma armação tem de modificar-se. Neste caso, não que importante, mas em uma animação que tem de modificar armações muito rapidamente, o método de paint() tenha de fazer uma pausa para criar aquele novo objeto cada vez. Deixando paint() para fazer o que faz a melhor pintura a tela - e cálculo de novos objetos anteriormente, pode fazer a pintura a mais eficiente possível. Isto é precisamente a mesma razão porque o objeto de Font também está em uma variável de exemplo.
Assim, quais são estes fios todos sobre? Porque são importantes para a animação?
Os fios são uma parte muito importante de Java e de programar Java. Maior os seus programas Java vêm e mais coisas que fazem, mais provavelmente é que quererá usar fios. Dependendo da sua experiência com sistemas operacionais e com ambientes dentro daqueles sistemas, pode ou pode não ter batido no conceito de fios, então vamos começar do começo.
Em primeiro lugar, a analogia. Um grupo de estudantes está em um ônibus, em uma viagem de campanha em algum lugar. Para passar o tempo, os professores conduzem cantar - ao longo. Enquanto a viagem progride, os estudantes cantam uma canção, então quando aquela canção se faz, cantam outra canção. Enquanto as partes diferentes do ônibus podem cantar canções diferentes, não soaria muito bem, portanto o canto de uma canção monopoliza o tempo até o seu feito, em que tempo outra canção pode começar.
Agora digamos tem dois ônibus; ambos estão na mesma via à viagem de campanha, ambos vão na mesma velocidade, e ambos são cheios de estudantes que cantam canções. Mas as canções que se cantam pelos estudantes no segundo ônibus não mexem nas canções que se cantam no primeiro ônibus; deste modo pode adquirir duas vezes mais canções cantadas no mesmo período de tempo cantando-os na paralela.
Os fios parecem-se com isto. Em um programa único roscado regular, o programa começa a realizar, dirige o seu código de inicialização, métodos de chamadas ou procedimentos, e continua dirigindo e processar até que seja completo ou até que o programa se saia. Aquele programa dirige em um fio-it's único um ônibus com todos os estudantes.
Multienfiar, como em Java, significa que várias partes diferentes do mesmo programa podem correr ao mesmo tempo, na paralela, sem mexer um em outro. Múltiplos fios, cada gerência por si mesmo, parecem-se com múltiplos ônibus com coisas diferentes que continuam em cada ônibus.
Aqui está um exemplo simples. Suponha que tem um cômputo longo perto da partida da execução de um programa. Este cômputo longo não pode ser necessário até depois na execução-it's do programa de fato tangencial ao ponto principal do programa, mas tem de fazer-se consequentemente. Em um programa único roscado, tem de esperar por aquele cômputo para terminar antes que o resto do programa possa continuar correndo. Em um sistema multiroscado, pode pôr aquele cômputo no seu próprio fio, e o resto do programa pode continuar correndo independentemente.
A animação é um exemplo da espécie da tarefa que precisa do seu próprio fio. Tome, por exemplo, aquele relógio digital applet, que tem um laço de while() infinito. Se não usasse fios, while() dirigiria no default o fio de sistema de Java, que também é responsável por tratar a pintura da tela, o procedimento com usuário introduziu como cliques de rato e cuidado de tudo interiormente - até agora. Infelizmente, contudo, se dirigir aquele laço de while() no fio de sistema principal, monopolizará recursos de todo o Java e prevenirá a pintura de algo mais incluindo - do acontecimento. Nunca veria de fato nada na tela porque Java se estaria sentando e esperando pelo laço de while() para terminar antes que fizesse algo mais. E isto não é o que quer.
Usando fios em Java, pode criar partes de um applet (ou aplicação) que a corrida nos seus próprios fios e aquelas partes dirigirão felizmente todos por si mesmos sem mexer em algo mais. Dependendo de quantos fios tem, pode taxar consequentemente o sistema para que todos eles corram mais devagar, mas todos eles ainda correrão independentemente.
Mesmo se não usar muitos deles, a utilização de fios no seu applets é uma boa prática de programação de Java. A regra geral de polegar de applets bem-comportado: Sempre que tenha qualquer bit do processamento que provavelmente continuará por muito tempo (como um laço de animação, ou um bocado do código que leva muito tempo para realizar), ponha-o em um fio.
Criar applets que o uso enfia é muito fácil. De fato, muitas das coisas básicas que tem de fazer para usar fios são somente boilerplate código que pode copiar e colar de um applet ao outro. Como é tão fácil, não há quase razão de não usar fios no seu applets, considerando os benefícios.
Há quatro modificações que tem de fazer para criar um applet que usa fios:
A primeira modificação é à primeira linha da sua definição de classe. Já adquiriu algo como isto:
public class MyAppletClass extends java.applet.Applet { ... }
Tem de modificá-lo para o seguinte:
public class MyAppletClass extends java.applet.Applet implements Runnable { ... }
O que isto faz? Inclui o suporte da interface de Runnable no seu applet. Se pensar o caminho atrás ao Dia 2, "A Programação orientada ao Objeto e Java", se lembrará de que as interfaces são um modo de reunir nomes de método comuns a classes diferentes, que então podem misturar-se em e implementaram classes diferentes interiores que têm de implementar aquele comportamento. Aqui, a interface de Runnable define o comportamento o seu applet tem de dirigir um fio; especialmente, dá-lhe uma definição à revelia do método de run(). Implementando Runnable, diz a outros que podem chamar o método de Run() nos seus exemplos.
O segundo passo deve acrescentar uma variável de exemplo para manter o fio deste applet. Chame-o algo do qual você gosta; é uma variável do tipo Thread (Thread é uma classe em java.lang, portanto não tem de importá-lo):
Thread runner;
Em terceiro lugar, acrescente um método de start() ou modifique o existente para que realmente apenas crie um novo fio e o comece gerência. Aqui está um exemplo típico de um método de start():
public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } }
Se modificar start() para fazer apenas criam um fio, onde faz o código que dirige os seus applet vão? Entra em um novo método, run(), que parece a isto:
public void run() { // what your applet actually does }
O seu método de run() de fato ignora a versão à revelia de run(), que adquire quando inclui a interface de Runnable com o seu applet. run() é um daqueles métodos padrão, como start() e paint(), que ignora nas suas próprias classes para adquirir o comportamento padrão.
run() pode conter algo que quer dirigir no fio separado: o código de inicialização, o laço real do seu applet ou algo mais que tem de correr no seu próprio fio. Também pode criar novos objetos e chamar métodos do interior de run(), e também correrão dentro daquele fio. O método de run() é o verdadeiro coração do seu applet.
Finalmente, agora que tem a gerência de fios e um método de start() para começá-los, deve acrescentar um método de stop() para suspender a execução daquele fio (e por isso tudo o que o applet faz no momento) quando o leitor deixa a página. stop(), como start(), é normalmente algo ao longo destas linhas:
public void stop() { if (runner != null) { runner.stop(); runner = null; } }
O método de stop() aqui faz duas coisas: para o fio da execução e também estabelece o runner variável do fio em null. Estabelecer a variável a null faz o Thread objetar que anteriormente contivesse disponível para a coleção de lixo para que o applet possa retirar-se da memória depois de certo período de tempo. Se o leitor voltar a esta página e este applet, o método de start() cria um novo fio e lança o applet mais uma vez.
E é isso! Quatro modificações básicas, e agora tem um applet bem-comportado que corre no seu próprio fio.
Vamos tomar outra olhada que DigitalClock applet, esta vez do ponto de vista de fios. A listagem 10.2 mostra que o código de applet mais uma vez.
A listagem 10.2. O DigitalClock applet, revisitado.
1: import java.awt.Graphics; 2: import java.awt.Font; 3: import java.util.Date; 4: 5: public class DigitalClock extends java.applet.Applet 6: implements Runnable { 7: 8: Font theFont = new Font("TimesRoman",Font.BOLD,24); 9: Date theDate; 10: Thread runner; 11: 12: public void start() { 13: if (runner == null) { 14: runner = new Thread(this); 15: runner.start(); 16: } 17: } 18: 19: public void stop() { 20: if (runner != null) { 21: runner.stop(); 21: runner = null; 22: } 23: } 24: 25: public void run() { 26: while (true) { 27: theDate = new Date(); 28: repaint(); 29: try { Thread.sleep(1000); } 30: catch (InterruptedException e) { } 31: } 32: } 33: 34: public void paint(Graphics g) { 35: g.setFont(theFont); 36: g.drawString(theDate.toString(),10,50); 37: } 38:}
Análise |
Vamos olhar para as linhas deste applet que criam e dirigem fios. Em primeiro lugar, olhe para a própria definição de classe em linhas 5 e 6; observe que a definição de classe inclui a interface de Runnable. Qualquer classe cria aquele uso fios deve incluir Runnable. |
A linha 10 define uma terceira variável de exemplo desta classe chamada runner do tipo Thread, que manterá o objeto de fio deste applet.
As linhas 12 para 23 definem o start() boilerplate e métodos de stop() que não fazem nada exceto criam e destroem fios. Estas definições de método podem ser essencialmente exatamente o mesmo da classe à classe porque tudo que fazem se funda a infraestrutura do próprio fio.
E, finalmente, a maior parte do trabalho do seu applet continua dentro do método de run() em linhas 25 para 32, como já discutimos na última vez quando olhamos para este applet. Dentro deste método é o laço de while infinito, as chamadas a repaint() e o método de sleep(), que coisas de pausas portanto só dirigem uma vez um segundo.
Se tenha seguido junto com esta lição e tenha tentado os exemplos quando vai, em vez de ler este livro sobre um avião ou na banheira, pode ter notado que quando o programa de relógio digital corre, de vez em quando há um bruxuleio aborrecido na animação. (Não que haja algo de errado com a leitura deste livro na banheira, mas não verá o bruxuleio se fizer isto, então somente confie em mim-there's um bruxuleio.) Isto não é um erro ou um erro no programa; de fato, aquele bruxuleio é um efeito de lado de criar a animação em Java. Como é realmente aborrecido, aprenderá como reduzir o bruxuleio nesta parte da lição de hoje para que as suas animações dirijam o detergente e pareçam melhores na tela.
O bruxuleio causa-se pelo modo que Java pinta e repinta cada armação de um applet. No início da lição de hoje, aprendeu que quando chama o método de repaint(), repaint() chama paint(). Isto não é verdade precisamente. Uma chamada a paint() realmente de fato ocorre em resposta a um repaint(), mas o que de fato acontece são os seguintes passos:
É o passo 2, a chamada a update(), que causa o bruxuleio de animação. Como a tela se compensa entre armações, as partes da tela que não modificam o alternativo rapidamente entre pint&-se e compensar-se. Daqui, piscada.
Há dois modos principais de evitar o bruxuleio no seu Java applets:
Se o segundo caminho parecer complicado, é porque é. O armazenar em buffer duplamente implica o desenho para uma superfície fora da tela de gráficos e logo cópia que superfície inteira à tela. Como é mais complicado, explorará aquele amanhã. Hoje vamos cobrir a solução mais fácil: anulação de update().
A causa de piscada está no método de update(). Para reduzir piscada, por isso, ignoram update(). Aqui está o que a versão à revelia de update() faz (vem da classe de Component, é parte do awt e é uma das superclasses da classe applet. Aprenderá mais sobre ele no Dia 13, "Criando Interfaces de Usuário com o awt"):
public void update(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, width, height); g.setColor(getForeground()); paint(g); }
Basicamente, update() compensa a tela (ou, para ser exato, enche o retângulo que limita do applet da cor de fundo), atrasa coisas ao normal, e logo chama paint(). Quando ignora update(), tem de lembrar-se destas duas coisas e assegurar-se que a sua versão de update() faz algo semelhante. Em duas seguintes seções, trabalhará por alguns exemplos de ignorar update() em casos diferentes para reduzir o bruxuleio.
A primeira solução para a redução de bruxuleio não é compensar a tela em absoluto. Isto só trabalha para algum applets, naturalmente. Aqui está um exemplo de um applet deste tipo. O ColorSwirl applet imprime uma cadeia única para a tela ("All the Swirly Colors"), mas aquela cadeia se apresenta em cores diferentes que se desbotam um em outro dinamicamente. Este applet bruxuleia terrivelmente quando se dirige. A listagem 10.3 mostra a fonte inicial deste applet, e a Figura 10.2 mostra o resultado.
A figura 10.2: O ColorSwirl applet.
A listagem 10.3. O ColorSwirl applet.
1: import java.awt.Graphics; 2: import java.awt.Color; 3: import java.awt.Font; 4: 5: public class ColorSwirl extends java.applet.Applet 6: implements Runnable { 7: 8: Font f = new Font("TimesRoman",Font.BOLD,48); 9: Color colors[] = new Color[50]; 10: Thread runThread; 11: 12: public void start() { 13: if (runThread == null) { 14: runThread = new Thread(this); 15: runThread.start(); 16: } 17: } 18: 19: public void stop() { 20: if (runThread != null) { 21: runThread.stop(); 22: runThread = null; 23: } 24: } 25: 26: public void run() { 27: 28: // initialize the color array 29: float c = 0; 30: for (int i = 0; i < colors.length; i++) { 31: colors[i] = 32: Color.getHSBColor(c, (float)1.0,(float)1.0); 33: c += .02; 34: } 35: 36: // cycle through the colors 37: int i = 0; 38: while (true) { 39: setForeground(colors[i]); 40: repaint(); 41: i++; 42: try { Thread.sleep(50); } 43: catch (InterruptedException e) { } 44: if (i == colors.length ) i = 0; 45: } 46: } 47: 48: public void paint(Graphics g) { 49: g.setFont(f); 50: g.drawString("All the Swirly Colors", 15, 50); 51: } 52: }]
Análise |
Há três novas coisas a observar sobre este applet que poderia parecer estranho para você: |
Agora que entende o que o applet faz, vamos fixar o bruxuleio. O bruxuleio aqui resulta porque cada vez o applet se pinta, há um momento onde a tela se compensa. Em vez do texto que anda de bicicleta asseadamente do vermelho a um rosa bonito a purpúreo, vai de vermelho a cinza, a rosa a cinza, a purpúreo a cinza, e assim por diante - não olhar muito bonito em absoluto.
Como a clareira de tela é tudo que isto causa ao problema, a solução é fácil: Ignore update() e retire a parte onde a tela se compensa. Realmente não precisa de compensar-se de qualquer maneira, porque nada se modifica exceto a cor do texto. Com o comportamento de clareira de tela retirado de update(), toda a atualização tem de fazer é chamar paint(). Aqui está a que o método de update() parece neste applet (quererá acrescentá-lo depois do método de paint() depois da linha 51):
public void update(Graphics g) { paint(g); }
Com isto - com uma pequena adição de três linhas - não mais bruxuleio. Não foi fácil?
Observar |
Se estiver seguindo junto com os exemplos no CD, o arquivo de ColorSwirl.java contém o applet original com o bruxuleio; ColorSwirl2.java tem a versão fixa. |
Para algum applets, não será bastante tão fácil como somente não clareira da tela. Com algumas espécies da animação, compensando a tela é necessário para a animação para trabalhar propriamente. Aqui está outro exemplo. Neste applet, chamado Checkers, um oval vermelho (uma parte de padrão em xadrez) move-se de um quadrado preto para um quadrado branco, como se em um tabuleiro de damas. A listagem 10.4 mostra o código deste applet, e a Figura 10.3 mostra o próprio applet.
A figura 10.3: O Checkers applet.
A listagem 10.4. O Checkers applet.
1: import java.awt.Graphics; 2: import java.awt.Color; 3: 4: public class Checkers extends java.applet.Applet 5: implements Runnable { 6: 7: Thread runner; 8: int xpos; 9: 10: public void start() { 11: if (runner == null) { 12: runner = new Thread(this); 13: runner.start(); 14: } 15: } 16: 17: public void stop() { 18: if (runner != null) { 19: runner.stop(); 20: runner = null; 21: } 22: } 23: 24: public void run() { 25: setBackground(Color.blue); 26: while (true) { 27: for (xpos = 5; xpos <= 105; xpos+=4) { 28: repaint(); 29: try { Thread.sleep(100); } 30: catch (InterruptedException e) { } 31: } 32: xpos = 5; 33: } 34: } 35: 36: public void paint(Graphics g) { 37: // Draw background 38: g.setColor(Color.black); 39: g.fillRect(0, 0, 100, 100); 40: g.setColor(Color.white); 41: g.fillRect(101, 0, 100, 100); 42: 43: // Draw checker 44: g.setColor(Color.red); 45: g.fillOval(xpos, 5, 90, 90); 46: } 47: }
Análise |
Aqui está uma passada rápida rápida do que este applet faz: Uma variável de exemplo, xpos, guarda a pista da posição inicial atual da padrão em xadrez (porque se move horizontalmente, o y fica constante e só as modificações de x; não precisamos de guardar a pista da posição de y). No método de run(), modifica o valor de x e repinte, esperando 100 milissegundos entre cada movimento. A padrão em xadrez então parece mover-se do lado abandonado da tela à direita, reinicializando atrás na sua posição original uma vez que bate no lado direito da tela. |
No método de paint() real, os quadrados de fundo pintam-se (um preto e um branco), e logo a padrão em xadrez desenha-se na sua posição atual.
Este applet, como o ColorSwirl applet, também tem um bruxuleio terrível. (Na linha 25, modifiquei a cor de fundo para o azul para acentuá-lo, portanto se dirigir este applet, verá definitivamente o bruxuleio.)
Contudo, a solução para a solução do problema de bruxuleio deste applet é mais difícil do que para o último, porque de fato quer compensar a tela antes que a seguinte armação se desenhe. De outra maneira, a padrão em xadrez vermelha não terá a aparência de deixar uma posição e mover-se ao outro; somente deixará uma sujeira vermelha de um lado do tabuleiro de damas ao outro.
Como vem em volta disto? Ainda compensa a tela, para adquirir o efeito de animação, mas, em vez de compensar a tela inteira cada vez, só compensa a parte que se modificou de fato de uma armação para o seguinte. Limitando desenhar novamente a só uma pequena área, pode eliminar um pouco do bruxuleio que adquire de desenhar novamente a tela inteira.
Para limitar o que se desenha novamente, precisa de umas coisas de par. Em primeiro lugar, precisa de um modo de restringir a área de desenho para que cada vez paint() se chame, só a parte que tem de desenhar-se novamente de fato se desenha novamente. Afortunadamente, isto é fácil usando o recorte chamado de um mecanismo. Tosquiar, parte da classe de gráficos, permite-lhe restringir a área de desenho a uma pequena porção da tela cheia; embora a tela inteira possa adquirir instruções de desenhar novamente, só as porções dentro da área de recorte se desenham de fato.
Novo termo |
O recorte restringe a área de desenho a alguma mais pequena porção da tela. |
A segunda coisa da qual precisa é um modo de guardar a pista da área real para desenhar novamente. Ambos que as bordas esquerdas e direitas da área de desenho modificam para cada armação da animação (um lado para desenhar o novo oval, o outro para apagar o bit do oval esquerdo da armação prévia), assim para guardar a pista daqueles dois valores de x, precisa de variáveis de exemplo tanto do lado abandonado como do direito.
Com aqueles dois conceitos em mente, vamos começar a modificar o Checkers applet só para desenhar novamente que necessidades desenhar-se novamente. Em primeiro lugar, acrescentará variáveis de exemplo das bordas esquerdas e direitas da área de desenho. Vamos chamar aquelas variáveis de exemplo ux1 e ux2 (u da atualização), onde ux1 é o lado abandonado da área para desenhar e ux2 o direito:
int ux1,ux2;
Agora vamos modificar o método de run() para que guarde a pista da área real a desenhar-se, que pensaria é fácil - somente atualizam cada lado de cada iteração da animação. Aqui, contudo, as coisas podem complicar-se por causa do modo que Java usa paint() e repaint().
O problema com a atualização das bordas da área de desenho com cada armação da animação consiste em que para cada chamada a repaint() pode não haver um paint() correspondente individual. Se os recursos de sistema se tornarem apertados (por causa de outros programas que correm no sistema ou por qualquer outra razão), paint() não pode realizar-se imediatamente e várias chamadas a paint() podem fazer fila esperando pela sua tendência para modificar os pixéis na tela. Neste caso, em vez de tentar fazer todas aquelas chamadas a paint() na ordem (e estar potencialmente atrás todo o tempo), Java equipara-se realizando só a chamada mais recente a paint() e omite tudo os outros.
Isto põe um problema difícil no Checkers applet. Se atualizar as bordas da área de desenho com cada chamada a repaint(), e um par chama a paint() omitem-se, termina com bits da superfície de desenho que não se atualiza em absoluto ou bits do oval (coloquialmente chamado "bostas") deixado para trás. Por causa de como repaint() e paint() trabalham em Java, não pode garantir que cada região de recorte se pintará um pouco consequentemente pode omitir-se. O modo de resolver isto não é reinicializar a região de recorte com algo novo cada passo, mas em vez disso reinicializar a região só se aquela região de fato se atualizou. Este caminho, se um par de chamadas a paint() se omitirem, a área a atualizar-se vai se tornar maior para cada armação, e quando paint() finalmente se alcança, tudo se repintará corretamente.
Sim, isto é o complexo horrifyingly. Se posso ter escrito este applet mais simplesmente, teria (e, de fato, realmente fi-lo tão simples como poderia depois de reescrever muito), mas sem este mecanismo o applet não se repintará corretamente (a minha primeira tentativa neste applet deixou bostas por todas as partes). Vamos dar passos por ele lentamente no código portanto pode adquirir um melhor aperto do que está acontecendo em cada passo.
Vamos começar com run(), onde cada armação da animação se realiza. Aqui está onde calcula cada lado da área de recorte baseada na velha posição do oval e a nova posição do oval. O valor de ux1 (o lado abandonado da área de desenho) é a posição x do oval prévio (xpos), e o valor de ux2 é a posição x do oval atual mais a largura daquele oval (90 pixéis neste exemplo).
Aqui está a que o velho método de run() pareceu:
public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos += 4) { repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } } xpos = 5; } }
Para cada passo no qual os movimentos ovais em direção ao direito, primeiro atualiza ux2 (a borda direita da área de desenho):
ux2 = xpos + 90;
Então, depois que o repaint() ocorreu, pode atualizar ux1 para refletir a velha posição x do oval. Contudo, quer atualizar este valor só se a pintura de fato aconteceu, portanto não termina de omitir bits da tela. Como pode contar se a pintura de fato aconteceu? Pode reinicializar ux1 em paint() a um valor dado (diga 0), e logo teste dentro de run() para ver se pode atualizar aquele valor ou se tem de esperar pelo paint() para ocorrer:
if (ux1 == 0) ux1 = xpos;
Finalmente, há uma outra modificação para fazer. Quando os alcances ovais que o lado direito da tela e recomposições atrás à sua posição original, há uma armação onde quer desenhar novamente a tela inteira em vez de criar uma região de recorte (de outra maneira, a imagem do oval permaneceria no lado direito da tela). Deste modo, neste caso, quer fazer que ux2 seja a largura cheia do applet. Aqui modificaremos a linha na qual somente instalamos para estabelecer o valor de ux2, usando uma afirmação de if para testar para ver se o oval está no lado abandonado da tela:
if (xpos == 5) ux2 = size().width; else ux2 = xpos + 90;
O método de size() usa-se para adquirir as dimensões do applet; size().width dá a largura cheia do applet para que a superfície de desenho inteira se atualize.
Aqui está a nova versão de run() com aquelas modificações no lugar:
public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos+=4) { if (xpos == 5) ux2 = size().width; else ux2 = xpos + 90; repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } if (ux1 == 0) ux1 = xpos; } xpos = 5; } }
Aqueles são as únicas modificações necessidades de run(). Vamos ignorar update() para limitar a região que se está pintando às bordas esquerdas e direitas da área de desenho que estabelece dentro de run(). para tosquiar a área de desenho a um retângulo específico, usar o método de clipRect(). clipRect(), como drawRect(), fillRect(), e clearRect(), define-se para Graphics objeta e toma quatro argumentos: x e posições iniciais de y, e a largura e altura da região.
Aqui está onde ux1 e ux2 entram no jogo. ux1 é o ponto x da esquina superior da região; então use ux2 para adquirir a largura da região subtraindo ux1 daquele valor. Os valores de y são o padrão y valores do oval, que não variam em absoluto (as partidas ovais na posição y 5 e fins em 95). Finalmente, para terminar update(), chama paint():
public void update(Graphics g) { g.clipRect(ux1, 5, ux2 - ux1, 95); paint(g); }
Observe que com a região de recorte no lugar, não tem de fazer nada ao método de paint() real. paint() progride e desenha à tela inteira cada vez, mas só as áreas dentro da região de recorte de fato se modificam onscreen.
Precisará de fazer uma modificação de paint(), de qualquer modo. Tem de atualizar a borda rastejadora de cada área de desenho dentro de paint() em caso de que várias chamadas a paint() se omitiram. Como testa para um valor de 0 dentro de run(), inside paint() você simplesmente pode reinicializar ux1 e ux2 a 0 depois de desenhar tudo:
ux1 = ux2 = 0;
Aqueles são as únicas modificações que tem de fazer a este applet para só desenhar as partes do applet que se modificou (e dirigir o caso onde algumas armações não se tornam atualizadas imediatamente). Embora isto não elimine totalmente piscada na animação, realmente reduz-o muito. Tente-o e ver. A listagem 10.5 mostra o código final do Checkers applet (chamou Checkers2.java).
A listagem 10.5. O Checkers final applet.
1: import java.awt.Graphics; 2: import java.awt.Color; 3: 4: public class Checkers2 extends java.applet.Applet implements Runnable { 5: 6: Thread runner; 7: int xpos; 8: int ux1,ux2; 9: 10: public void start() { 11: if (runner == null) { 12: runner = new Thread(this); 13: runner.start(); 14: } 15: } 16: 17: public void stop() { 18: if (runner != null) { 19: runner.stop(); 20: runner = null; 21: } 22: } 23: 24: public void run() { 25: setBackground(Color.blue); 26: while (true) { 27: for (xpos = 5; xpos <= 105; xpos+=4) { 28: if (xpos == 5) ux2 = size().width; 29: else ux2 = xpos + 90; 30: repaint(); 31: try { Thread.sleep(100); } 32: catch (InterruptedException e) { } 33: if (ux1 == 0) ux1 = xpos; 34: } 35: xpos = 5; 36: } 37: } 38: 39: public void update(Graphics g) { 40: g.clipRect(ux1, 5, ux2 - ux1, 95); 41: paint(g); 42: } 43: 44: public void paint(Graphics g) { 45: // Draw background 46: g.setColor(Color.black); 47: g.fillRect(0, 0, 100, 100); 48: g.setColor(Color.white); 49: g.fillRect(101, 0, 100, 100); 50: 51: // Draw checker 52: g.setColor(Color.red); 53: g.fillOval(xpos, 5, 90, 90); 54: 55: // reset the drawing area 56: ux1 = ux2 = 0; 57: } 58:}
Congratulações com passagem por Dia 10! Este dia foi um bocado áspero; aprendeu muito, e poderia parecer esmagador. Aprendeu sobre uma pletora de métodos a usar e ignorar-start(), stop(), paint(), repaint(), run() e update() - e adquiriu uma fundação básica em criação e utilização de fios. Outro do que o manejo de imagens de mapa de bits, sobre as quais aprenderá amanhã, agora tem o contexto básico para criar quase qualquer animação que quer em Java.
Porque todas as vias indiretas com paint(), repaint(), update() e tudo isto? Não porque ter um método de pintura simples que põe o material na tela quando o quer lá? | |
Java awt permite-lhe aninhar superfícies drawables dentro de outras superfícies drawables. Quando um paint() se realiza, todas as partes do sistema desenham-se novamente, começando da superfície externa e descendo na mais aninhada. Como o desenho do seu applet se realiza ao mesmo tempo todo o resto desenha-se, o seu applet não adquire nenhum tratamento especial. O seu applet vai se pintar quando todo o resto se pintar. Embora com este sistema sacrifique um pouco da imediação da pintura imediata, permite ao seu applet coexistir com o resto do sistema mais limpamente. | |
Fios de Java são como fios em outros sistemas? | |
Os fios de Java foram sob o efeito de outros sistemas de fio, e se estiver acostumado a trabalhar com fios, muitos dos conceitos em fios de Java serão muito familiares para você. Aprendeu os fundamentos hoje; aprenderá mais a próxima semana no Dia 18. | |
Quando um applet usa fios, somente tenho de dizer ao fio começar e começa, e diga-lhe parar e para? É isso? Não tenho de testar nada nos meus laços ou guardar a pista do seu estado? Somente para? | |
Somente para. Quando põe o seu applet em um fio, Java pode controlar a execução do seu applet muito mais prontamente. Fazendo o fio parar, o seu applet somente deixa de correr, e logo retoma quando o fio lança novamente. Sim, é tudo automático. Arrumado, não é? | |
O ColorSwirl applet parece expor só cinco ou seis cores, que não é muito com redemoinhos. O que está acontecendo aqui? | |
Isto é o mesmo problema no qual bateu ontem. Em alguns sistemas, não poderiam haver bastantes cores disponíveis para ser capazes de expor todos eles confiantemente. Se bate neste problema, além do upgrade do seu hardware, poderia tentar deixar outras aplicações que correm no seu sistema aquela cor de uso. Outros browseres ou os instrumentos a cores especialmente poderiam ser cores de hogging que aquele Java quer ser capaz de usar. | |
Mesmo com as modificações fez, o Checkers applet ainda bruxuleia. | |
E, infelizmente, continuará fazendo assim. Reduzir o tamanho da área de desenho usando o recorte realmente reduz piscada, mas não o para inteiramente. Para muitos applets, usando qualquer dos métodos descritos hoje pode ser bastante para reduzir o bruxuleio de animação ao ponto onde o seu applet parece bom. Para adquirir a animação totalmente sem bruxuleios, precisará de usar uma técnica chamada armazenar em buffer duplamente, sobre o qual aprenderá amanhã. |