Programação para a Plataforma Android – Aula 6 Gráficos 2D • Representando cores • Desenhando formas simples • Lindando com eventos de baixo nível • Como criar animações simples? • Como adicionar um key‐pad à aplicação? • Representando iterações entre aKvidades Cores • Android representa cores como inteiros de 32 bits, em formato hexadecimal. 0x FF 32 15 21 lue B e n e e r G , Red Mas o . s o i v b ó o ã s ? quê é alpha BLUE GREEN RED ALPHA Exemplos de Cores 0x3500FFFF 0xFF00FFFF 0xFF0000FF 0x000000FF View • A classe básica que descreve elementos gráficos é View. • Essa classe possui o método onDraw, que determina o que será desenhado na tela do disposiKvo. • Esse método recebe um objeto do Kpo Canvas, que representa a tela to disposiKvo. Gráficos de Baixo Nível public class GraphicsView extends View { public GraphicsView(Context context) { ue a classe q O super(context); hicsView p a r G } desenha? @Override protected void onDraw(Canvas canvas) { Path circle = new Path(); circle.addCircle(150, 150, 60, DirecKon.CW); Como usar Paint aPaint = new Paint(); GraphicsView? aPaint.setColor(Color.LTGRAY); canvas.drawPath(circle, aPaint); Paint bPaint = new Paint(); bPaint.setColor(Color.BLUE); canvas.drawTextOnPath("Mais vale um pássaro na mão que dois voando!", circle, 0, 20, bPaint); } } GraphicsView.java Gráficos de Baixo Nível Como usar GraphicsView? ExampleGraphics.java Views são usadas como Layouts public class ExampleGraphics extends AcKvity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new GraphicsView(this)); } } O que quer dizer passar “this” para a nossa View? GraphicsView.java Mais um Exemplo public class GraphicsView extends View { public GraphicsView(Context context) { super(context); E o que } obteríamos com @Override protected void onDraw(Canvas canvas) { esse comando? Path circle = new Path(); circle.addRect(2, 2, 100, 100, Direc=on.CW); Paint aPaint = new Paint(); aPaint.setColor(Color.LTGRAY); canvas.drawPath(circle, aPaint); Paint bPaint = new Paint(); bPaint.setColor(Color.BLUE); canvas.drawTextOnPath("Mais vale um pássaro na mão que dois voando!", circle, 0, 20, bPaint); } } Dica Legal: tamanho da janela private int height; private int width; public GraphicsView(Context context) { super(context); DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); display.getMetrics(metrics); height = metrics.heightPixels; width = metrics.widthPixels; } Mais um Exemplo public class GraphicsView extends View { public GraphicsView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { Path circle = new Path(); circle.addRect(2, 2, 100, 100, Direc=on.CW); Paint aPaint = new Paint(); aPaint.setColor(Color.LTGRAY); O que mais canvas.drawPath(circle, aPaint); podemos fazer Paint bPaint = new Paint(); com Path? bPaint.setColor(Color.BLUE); canvas.drawTextOnPath("Mais vale um pássaro na mão que dois voando!", circle, 0, 20, bPaint); } } Drawables • Drawables são enKdades que podem ser visualizadas na tela do disposiKvo. • Em geral esses recursos ficam armazenados na pasta drawable. • Exemplos incluem figuras (jpeg, png, svg), padrões de cores, desenhos vetoriais, camadas, etc. drawable/gradient.xml Exemplo de Drawable • Esse arquivo será usado como pano de fundo em nosso exemplo corrente: <?xml version="1.0" encoding="uo‐8"?> <shape xmlns:android= "hrp://schemas.android.com/apk/res/android"> <gradient O que esse android:startColor="#FFFFFF” drawable android:endColor="#808080" faz? android:angle="270" /> </shape> drawable/gradient.xml Exemplo de Drawable • Esse arquivo será usado como pano de fundo em nosso exemplo corrente: <?xml version="1.0" encoding="uo‐8"?> <shape xmlns:android= "hrp://schemas.android.com/apk/res/android"> <gradient android:startColor="#FFFFFF” android:endColor="#808080" android:angle="270" /> </shape> sáu o E com lo? GraphicsView.java Exemplo de Drawable • Esse arquivo será usado como pano de fundo em nosso exemplo corrente: public class GraphicsView extends View { public GraphicsView(Context context) { super(context); setBackgroundResource(R.drawable.gradient); } } ConKnuando nosso Sudoku • Até agora temos um aplicaKvo que contém um belo layout inicial, e uma caixa “about”! Sudoku.java Começando um Jogo Novo private void openNewGameDialog() { new AlertDialog.Builder(this).setTitle(R.string.new_game_Ktle) .setItems( R.array.difficulty, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialoginterface, int i) { startGame(i); } } ue q m E eo t s i s ).show(); con do o t é } m ame? G start Sudoku.java Usando a IDE De que outras formas podemos usar a IDE para sermos mais produtivos? private void startGame(int i) { Log.d(TAG, "clicked on " + i); Intent intent = new Intent(Sudoku.this, Game.class); intent.putExtra(Game.KEY_DIFFICULTY, i); startAcKvity(intent); } • Esse programa não irá compilar, pois a classe Game ainda não foi definida. – Mas podemos defini‐la facilmente, usando os ganchos deixados pela interface de desenvolvimento. Setup Inicial • A classe Game, sendo uma aKvidade, precisa ser registrada no manifesto. <acKvity android:name=".Game" android:label="@string/game_Ktle"/> AndroidManifest.xml • Criemos também algumas strings para usarmos em nossa implementação de Game. <string name="game_Ktle">Game</string> <string name="no_moves_label">No moves</string> <string name="keypad_Ktle">Keypad</string> strings.xml Game.java A Classe Game public class Game extends AcKvity { Que dois private staKc final String TAG = "Sudoku"; mentos u g r a private int puzzle[] = new int[9 * 9]; ses? s e o ã s private PuzzleView puzzleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); int diff = getIntent().getIntExtra(KEY_DIFFICULTY, DIFFICULTY_EASY); Precisamos agora definir o jogo propriamente dito. } } O que um “Game” sabe fazer? Quais são os passos para criarmos o jogo? Ps.: Que bandeira e essa? Game.java A Classe Game public class Game extends AcKvity { private staKc final String TAG = "Sudoku"; private int puzzle[] = new int[9 * 9]; private PuzzleView puzzleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); int diff = getIntent().getIntExtra(KEY_DIFFICULTY, DIFFICULTY_EASY); Mas, antes de proseguirmos, por que estamos representando uma matriz desse jeito? Dica: tem a ver com eficiência. } } public class Game extends AcKvity { public staKc final int DIFFICULTY_EASY = 0; public staKc final int DIFFICULTY_MEDIUM = 1; public staKc final int DIFFICULTY_HARD = 2; A classe PuzzleView ainda não existe. private staKc final String TAG = "Sudoku"; private int puzzle[] = new int[9 * 9]; private PuzzleView puzzleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); int diff = getIntent().getIntExtra(KEY_DIFFICULTY, DIFFICULTY_EASY); puzzle = getPuzzle(diff); Vários métodos calculateUsedTiles(); ainda não puzzleView = new PuzzleView(this); compilam. setContentView(puzzleView); puzzleView.requestFocus(); } } Game.java A Classe Game PuzzleView • A classe PuzzleView é reponsável por desenhar o tabuleiro do jogo. • Qual o “super Kpo” dessa classe? • O que ela precisa saber fazer? Puzzle View PuzzleView.java public class PuzzleView extends View { private staKc final String TAG = "Sudoku"; private final Game game; public PuzzleView(Context context) { super(context); this.game = (Game) context; setFocusable(true); setFocusableInTouchMode(true); } Agora precisamo s descobrir o tamanho de um quadrado do sudoku. Como fazemos isto? protected void onSizeChanged(int w, int h, int oldw, int oldh) {} } PuzzleView.java Computando Pequenos Quadrados private float width; // width of one Kle private float height; // height of one Kle private int selX; // X index of selecKon private int selY; // Y index of selecKon private final Rect selRect = new Rect(); @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { width = w / 9f; height = h / 9f; getRect(selX, selY, selRect); Log.d(TAG, "onSizeChanged: width " + width + ", height " + height); super.onSizeChanged(w, h, oldw, oldh); } private void getRect(int x, int y, Rect rect) { rect.set((int) (x * width), (int) (y * height), (int) (x * width + width), (int) (y * height + height)); } PuzzleView.java Marcando a Seleção private float width; // width of one Kle private float height; // height of one Kle private int selX; // X index of selecKon private int selY; // Y index of selecKon private final Rect selRect = new Rect(); rá útil e s e t r a p a s Es is somente ma ara tarde, mas p se que serve es código? @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { width = w / 9f; height = h / 9f; getRect(selX, selY, selRect); Log.d(TAG, "onSizeChanged: width " + width + ", height " + height); super.onSizeChanged(w, h, oldw, oldh); } private void getRect(int x, int y, Rect rect) { rect.set((int) (x * width), (int) (y * height), (int) (x * width + width), (int) (y * height + height)); } O Tabuleiro sos Quais os pas o envolvidos n desenho do tabuleiro? Seria possível estabelecermos uma sequência de passos para esse desenho? O que são as cores dos pequenos retângulos? PuzzleView.java Desenhando o Tabuleiro @Override Onde protected void onDraw(Canvas canvas) { deveríamos // Desenhe o fundo... definir as Paint background = new Paint(); cores? background.setColor(getResources().getColor( R.color.puzzle_background)); canvas.drawRect(0, 0, getWidth(), getHeight(), background); // Desenhe as raias... // Desenhe os números... // Desenhe as dicas... // Desenhe a área selecionada… } values/colors.xml Cores como recursos E por enquanto o que será desenhado? <?xml version="1.0" encoding="u@‐8"?> <resources> <color name="background">#3500ffff</color> <color name="puzzle_background">#ffe6f0ff</color> <color name="puzzle_hilite">#ffffffff</color> <color name="puzzle_light">#64c6d4ef</color> <color name="puzzle_dark">#6456648f</color> <color name="puzzle_foreground">#ff000000</color> <color name="puzzle_hint_0">#64ff0000</color> <color name="puzzle_hint_1">#6400ff80</color> <color name="puzzle_hint_2">#2000ff80</color> <color name="puzzle_selected">#64ff8000</color> </resources> Desenhando o Tabuleiro Como desenhar a grade do sudoku? e: Lembre-s ades r g s o m e t e maiores . menores Como desenha ra grade menor? PuzzleView.java Auto‐Relevo Paint dark = new Paint(); dark.setColor(getResources().getColor(R.color.puzzle_dark)); Paint hilite = new Paint(); hilite.setColor(getResources().getColor(R.color.puzzle_hilite)); Paint light = new Paint(); ão s e u q r o P light.setColor(getResources().getColor(R.color.puzzle_light)); sempre as d a h n e s e d ? // Draw the minor grid lines s a h n i l s dua for (int i = 0; i < 9; i++) { canvas.drawLine(0, i * height, getWidth(), i * height, light); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), light); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); } PuzzleView.java Auto‐Relevo Paint dark = new Paint(); dark.setColor(getResources().getColor(R.color.puzzle_dark)); Paint hilite = new Paint(); hilite.setColor(getResources().getColor(R.color.puzzle_hilite)); Paint light = new Paint(); light.setColor(getResources().getColor(R.color.puzzle_light)); // Draw the minor grid lines for (int i = 0; i < 9; i++) { canvas.drawLine(0, i * height, getWidth(), i * height, light); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), light); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); } Como a desenhar or? i a m e d a gr Mas ainda falta o mais difícil: desenhar os números! Grade Maior for (int i = 0; i < 9; i++) { canvas.drawLine(0, i * height, getWidth(), i * height, light); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), light); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); if (i % 3 == 0) { canvas.drawLine(0, i * height, getWidth(), i * height, dark); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), dark); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); } } PuzzleView.java PuzzleView.java Preparando terreno para os números Paint foreground = new Paint(Paint.ANTI_ALIAS_FLAG); foreground.setColor(getResources(). getColor(R.color.puzzle_foreground)); O que esses comandos foreground.setStyle(Style.FILL); estão foreground.setTextSize(height * 0.75f); fazendo? foreground.setTextScaleX(width / height); foreground.setTextAlign(Paint.Align.CENTER); O que é aliasing? Como representar e armazenar os números? E como desenhar os números? PuzzleView.java Preparando terreno para os números Paint foreground = new Paint(Paint.ANTI_ALIAS_FLAG); foreground.setColor(getResources(). getColor(R.color.puzzle_foreground)); foreground.setStyle(Style.FILL); foreground.setTextSize(height * 0.75f); foreground.setTextScaleX(width / height); foreground.setTextAlign(Paint.Align.CENTER); O que é aliasing? PuzzleView.java Desenhando Números // Draw the number in the center of the Zle FontMetrics fm = foreground.getFontMetrics(); // Centering in X: use alignment (and X at midpoint) float x = width / 2; // Centering in Y: measure ascent/descent first Como ficaria o float y = height / 2 ‐ (fm.ascent + fm.descent) / 2; tabuleiro sem isso? for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { canvas.drawText(this.game.getTileString(i, j), i * width + x, j * height + y, foreground); } ente Método altam da a } misterioso ain ser escrito. Aspect RaKo • Esse ajuste, por height / 2 (fm.ascent + fm.descent) / 2, é necessário, senão estaríamos colocando a base da fonte no centro do ladrilho. O que temos por enquanto • Quatro Kpos de linhas, formando dois tamanhos de grades, são desenhados. • Números são desenhados entre as grades. • Mas ainda falta interagir com o usuário. • E falta a lógica do jogo! Selecionando Células • Seria interessante que o usuário pudesse selecionar uma célula do tabuleiro. 1. A célula selecionada deveria ser marcada com uma cor diferente. 2. O usuário deveria ser capaz de mover a célula selecionada. eiro m i r p s o m e t n e T resolver o mo problema 1: co a marcar a célul selecionada? Como colori r essa c élula? Guardando a célula selecionada • Retângulo são muito importantes em programação gráfica. – Android possui até mesmo uma classe para representá‐los: Rect.java. PuzzleView.java (ainda no método onDraw) Como se uma á lecionar rea retang ular? Log.d(TAG, "selRect=" + selRect); Paint selected = new Paint(); selected.setColor(getResources().getColor(R.color.puzzle_selected)); canvas.drawRect(selRect, selected); O que é esse objeto mesmo? Selecionando Retângulos PuzzleView.java Como o método select é chamado? private void select(int x, int y) { invalidate(selRect); selX = Math.min(Math.max(x, 0), 8); selY = Math.min(Math.max(y, 0), 8); getRect(selX, selY, selRect); invalidate(selRect); ngular a t e r a e r á A } como um Qual é o uso de invalidate nesse exemplo? funciona cebe e r e u q r o s cur ário. u s u o d s o t even ntar e m i v o m o Com ? esse cursor Movimentando o Cursor public boolean onKeyDown(int keyCode, KeyEvent event) { Log.d(TAG, "onKeyDown: keycode=" + keyCode + ", event=" + event); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: Essas select(selX, selY ‐ 1); variáveis são break; parte do case KeyEvent.KEYCODE_DPAD_DOWN: estado de select(selX, selY + 1); PuzzleView break; case KeyEvent.KEYCODE_DPAD_LEFT: O que é o select(selX ‐ 1, selY); estado de um break; objeto? case KeyEvent.KEYCODE_DPAD_RIGHT: select(selX + 1, selY); break; default: return super.onKeyDown(keyCode, event); } return true; } PuzzleView.java Como os inserir s? o r e m ú n Inserindo os Números PuzzleView.java case KeyEvent.KEYCODE_0: case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break; case KeyEvent.KEYCODE_1: setSelectedTile(1); break; case KeyEvent.KEYCODE_2: setSelectedTile(2); break; case KeyEvent.KEYCODE_3: setSelectedTile(3); break; case KeyEvent.KEYCODE_4: setSelectedTile(4); break; case KeyEvent.KEYCODE_5: setSelectedTile(5); break; case KeyEvent.KEYCODE_6: setSelectedTile(6); break; case KeyEvent.KEYCODE_7: setSelectedTile(7); break; case KeyEvent.KEYCODE_8: setSelectedTile(8); break; oé d o t é case KeyEvent.KEYCODE_9: setSelectedTile(9); break; m Que … esse? break; Ladrinhos válidos e inválidos PuzzleView.java public void setSelectedTile(int Kle) { if (game.setTileIfValid(selX, selY, Kle)) { invalidate(); } else { Log.d(TAG, "setSelectedTile: invalid: " + Kle); } } E os Argh… mais um ai método v e s es e u q misterioso para a Só h l pi a a r a implementarmos. p ivos t i s o p s i d têm que não teclas? MúlKplos DisposiKvos PuzzleView.java case KeyEvent.KEYCODE_0: case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break; case KeyEvent.KEYCODE_1: setSelectedTile(1); break; case KeyEvent.KEYCODE_2: setSelectedTile(2); break; case KeyEvent.KEYCODE_3: setSelectedTile(3); break; case KeyEvent.KEYCODE_4: setSelectedTile(4); break; case KeyEvent.KEYCODE_5: setSelectedTile(5); break; case KeyEvent.KEYCODE_6: setSelectedTile(6); break; case KeyEvent.KEYCODE_7: setSelectedTile(7); break; case KeyEvent.KEYCODE_8: setSelectedTile(8); break; case KeyEvent.KEYCODE_9: setSelectedTile(9); break; case KeyEvent.KEYCODE_ENTER: o aremos F d… case KeyEvent.KEYCODE_DPAD_CENTER: a P y e K show er v s game.showKeypadOrError(selX, selY); o m a is. V o p e d break; tela! a nossa Respiremos um Pouco • Vamos definir esse método game.getTileString para vermos o que temos até aqui: Game.java public String getTileString(int i, int j) { return "F"; } m a h c a s ê c O que vo agora? os m e t e u q Muitos F’s! • Vamos definir esse método game.getTileString para vermos o que temos até aqui: Game.java public String getTileString(int i, int j) { return "F"; } s agora mo Precisa eventos m lidar co onde r o P . e de toqu s? o m a ç e com Eventos de Toque PuzzleView.java @Override public boolean onTouchEvent(MoKonEvent event) { if (event.getAcKon() != MoKonEvent.ACTION_DOWN) return super.onTouchEvent(event); select((int) (event.getX() / width), (int) (event.getY() / height)); game.showKeypadOrError(selX, selY); Log.d(TAG, "onTouchEvent: x " + selX + ", y " + selY); return true; Sudoku é um jogo } bem difícil. Como podemos ajudar o usuário? Implemente um sistema de “dicas”. Cores de Ajuda • Podemos colorir os ladrilhos de forma diferente, dependendo de quantos valores possíveis cada ladrilho pode assumir. Dicas PuzzleView.java (dentro do método onDraw) // A cor da dica é baseada no número de opções restantes Paint hint = new Paint(); int c[] = { getResources().getColor(R.color.puzzle_hint_0), getResources().getColor(R.color.puzzle_hint_1), getResources().getColor(R.color.puzzle_hint_2), }; xar i e d s o Rect r = new Rect(); Vam odo t é m e s for (int i = 0; i < 9; i++) { es pois e d for (int j = 0; j < 9; j++) { a r a p . int movesleŽ = 9 ‐ game.getUsedTiles(i, j).length; também if (movesleŽ < c.length) { getRect(i, j, r); hint.setColor(c[movesleŽ]); Mas o que canvas.drawRect(r, hint); vocês acham } que ele faz? } } Efeitos Especiais Números inválidos: Se o usuário tentar entrar números inválidos, que já foram usados na linha ou na coluna, então façamos a tela tremer por um segundo. Idéias?! Não é necessário usar o acelerômetro ou qualquer coisa desse tipo.! Animações Simples • Android permite‐nos definir efeitos de animação simples em XML. • Podemos chamar esses efeitos a parKr de setSelectedTile: PuzzleView.java public void setSelectedTile(int Kle) { E o que if (game.setTileIfValid(selX, selY, Kle)) { sse e a i r e s invalidate(); ? recurso } else { Log.d(TAG, "setSelectedTile: invalid: " + Kle); startAnimaKon(AnimaKonUKls.loadAnimaKon(game, R.anim.shake)); } } Animação em XML anim/shake.xml <?xml version="1.0" encoding="uo‐8"?> <translate xmlns:android= "hrp://schemas.android.com/apk/res/android" android:fromXDelta="0" android:toXDelta="10" android:duraKon="1000" android:interpolator="@anim/cycle_7" /> • O número de vezes, e a aceleração de uma animação também são definidas em um arquivo XML. RepeKções da Animação anim/cycle_7.xml se s e e u q O ador l o p r e t in faz? <?xml version="1.0" encoding="uo‐8"?> <cycleInterpolator xmlns:android= "hrp://schemas.android.com/apk/res/android" android:cycles="7" /> Ok, avancemos: podemos ajudar o usuário com um teclado virtual. Key Pad Tela de teclas: A tela de teclas deve aparecer sempre que o usuário apertar o seletor do telefone. A tela permite que o usuário escolha o número que será armazenado naquela posição. ao i r e s o Com essa d t u o y la tela de teclas? <?xml version="1.0" encoding="u@‐8"?> <TableLayout xmlns:android="hdp://schemas.android.com/apk/res/android" android:id="@+id/keypad" android:orientaZon="verZcal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:stretchColumns="*"> <TableRow> <Buron android:id="@+id/keypad_1" android:text="1"> </Buron> <Buron android:id="@+id/keypad_2" android:text="2"> </Buron> <Buron android:id="@+id/keypad_3" android:text="3"> </Buron> </TableRow> <TableRow> <Buron android:id="@+id/keypad_4" android:text="4"> </Buron> <Buron android:id="@+id/keypad_5" android:text="5"> </Buron> <Buron android:id="@+id/keypad_6" android:text="6"> </Buron> </TableRow> <TableRow> <Buron android:id="@+id/keypad_7" android:text="7"> </Buron> <Buron android:id="@+id/keypad_8" android:text="8"> </Buron> <Buron android:id="@+id/keypad_9" android:text="9"> </Buron> </TableRow> </TableLayout> layout/keypad.xml Layout de Tabelas Quem “cria” o Key Pad? Como essa atividade é invocada? Chamando o Keypad Game.java protected void showKeypadOrError(int x, int y) { int Kles[] = getUsedTiles(x, y); if (Kles.length == 9) { Toast toast = Toast.makeText(this, R.string.no_moves_label, Toast.LENGTH_SHORT); toast.setGravity(Gravity.CENTER, 0, 0); Para que toast.show(); serve esse } else { código? Dialog v = new Keypad(this, =les, puzzleView); v.show(); Como é a Gostaríamos de habilitar } implementasomente aqueles ção da classe } números que são válidos para a posição. KeyPad? Keypad.java A Classe Key Pad public class Keypad extends Dialog { private final View keys[] = new View[9]; private View keypad; private final int useds[]; private final PuzzleView puzzleView; public Keypad(Context context, int useds[], PuzzleView puzzleView) { super(context); mo é a o C this.useds = useds; implementa this.puzzleView = puzzleView; ção de } onCreate? protected void onCreate(Bundle savedInstanceState) { ... } ... } Keypad.onCreate() Keypad.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(R.string.keypad_Ktle); setContentView(R.layout.keypad); findViews(); for (int element : useds) { if (element != 0) keys[element ‐ 1].setVisibility(View.INVISIBLE); } Para que setListeners(); Implementemos os “escudadores serve esse } de eventos”. código? Keypad.java Listeners private void setListeners() { for (int i = 0; i < keys.length; i++) { final int t = i + 1; keys[i].setOnClickListener(new View.OnClickListener() { public void onClick(View v) { returnResult(t); } }); } keypad.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { E como seria a returnResult(0); implementação } de returnResult? }); } Listeners private void setListeners() { for (int i = 0; i < keys.length; i++) { final int t = i + 1; keys[i].setOnClickListener(new View.OnClickListener() { Keypad.java public void onClick(View v) { returnResult(t); private void returnResult(int Kle) { } puzzleView.setSelectedTile(Kle); }); dismiss(); } } keypad.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { qual a E returnResult(0); semântica de } dismiss()? }); } Associando eventos a views private void setListeners() { for (int i = 0; i < keys.length; i++) { final int t = i + 1; keys[i].setOnClickListener(new View.OnClickListener() { public void onClick(View v) { O que são esses returnResult(t); objetos } chamados }); “keys”? } keypad.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { returnResult(0); } }); } Keypad.java Views e Teclas Keypad.java private final View keys[] = new View[9]; private void findViews() { keypad = findViewById(R.id.keypad); keys[0] = findViewById(R.id.keypad_1); keys[1] = findViewById(R.id.keypad_2); keys[2] = findViewById(R.id.keypad_3); keys[3] = findViewById(R.id.keypad_4); keys[4] = findViewById(R.id.keypad_5); keys[5] = findViewById(R.id.keypad_6); keys[6] = findViewById(R.id.keypad_7); keys[7] = findViewById(R.id.keypad_8); keys[8] = findViewById(R.id.keypad_9); } Agora devemos concentrar-nos na lógica do jogo. Primeiro, precisamos implementar setTileIfValid setTileIfValid Game.java protected boolean setTileIfValid(int x, int y, int value) { int Kles[] = getUsedTiles(x, y); if (value != 0) { Em poucas for (int Kle : Kles) { palavras: o que if (Kle == value) esse método return false; está fazendo? } } E como seria setTile(x, y, value); a implementaç ão calculateUsedTiles(); de return true; getUsedTile s? } setTileIfValid Qual é o modelo nosso deprotected boolean setTileIfValid(int x, int y, int value) { doku? Su int Kles[] = getUsedTiles(x, y); if (value != 0) { Game.java for (int Kle : Kles) { private final int used[][][] = new int[9][9][]; if (Kle == value) return false; protected int[] getUsedTiles(int x, int y) { } return used[x][y]; } } setTile(x, y, value); calculateUsedTiles(); return true; } Inserindo os Números Game.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); int diff = getIntent().getIntExtra(KEY_DIFFICULTY, DIFFICULTY_EASY); puzzle = getPuzzle(diff); Sempre que atualizamos calculateUsedTiles(); o tabuleiro do jogo, o puzzleView = new PuzzleView(this); método onCreate é setContentView(puzzleView); chamado, e é preciso puzzleView.requestFocus(); calcular quais números } não são mais válidos para cada célula. Como fazer isso? Calculando ladrilhos usados Game.java Não é problema métodos diferentes com nomes iguais? private void calculateUsedTiles() { for (int x = 0; x < 9; x++) { for (int y = 0; y < 9; y++) { used[x][y] = calculateUsedTiles(x, y); } Como calcular quais } ladrilhos não podem } ser usados para uma certa posição do Sudoku? private int[] calculateUsedTiles(int x, int y) { int c[] = new int[9]; // horizontal for (int i = 0; i < 9; i++) { if (i == y) con=nue; int t = getTile(x, i); if (t != 0) c[t ‐ 1] = t; } // verKcal for (int i = 0; i < 9; i++) { if (i == x) con=nue; int t = getTile(i, y); if (t != 0) c[t ‐ 1] = t; } // same cell block int startx = (x / 3) * 3; int starty = (y / 3) * 3; for (int i = startx; i < startx + 3; i++) { for (int j = starty; j < starty + 3; j++) { if (i == x && j == y) con=nue; int t = getTile(i, j); if (t != 0) c[t ‐ 1] = t; } } // compress int nused = 0; for (int t : c) { if (t != 0) nused++; } int c1[] = new int[nused]; nused = 0; for (int t : c) { if (t != 0) c1[nused++] = t; } return c1; } for (int i = 0; i < 9; i++) { if (i == y) conKnue; int t = getTile(x, i); if (t != 0) c[t ‐ 1] = t; } Game.java for (int i = 0; i < 9; i++) { if (i == x) conKnue; int t = getTile(i, y); if (t != 0) c[t ‐ 1] = t; } int startx = (x / 3) * 3; int starty = (y / 3) * 3; for (int i = startx; i < startx + 3; i++) { for (int j = starty; j < starty + 3; j++) { if (i == x && j == y) conKnue; int t = getTile(i, j); Como remover if (t != 0) os zeros desse c[t ‐ 1] = t; arranjo de } posições usadas? } private int[] calculateUsedTiles(int x, int y) { int c[] = new int[9]; // horizontal for (int i = 0; i < 9; i++) { if (i == y) conKnue; int t = getTile(x, i); if (t != 0) c[t ‐ 1] = t; } // verKcal for (int i = 0; i < 9; i++) { if (i == x) conKnue; int t = getTile(i, y); if (t != 0) c[t ‐ 1] = t; } // same cell block int startx = (x / 3) * 3; int starty = (y / 3) * 3; for (int i = startx; i < startx + 3; i++) { for (int j = starty; j < starty + 3; j++) { if (i == x && j == y) conKnue; int t = getTile(i, j); if (t != 0) c[t ‐ 1] = t; } } // compress int nused = 0; for (int t : c) { if (t != 0) nused++; } int c1[] = new int[nused]; nused = 0; for (int t : c) { if (t != 0) c1[nused++] = t; } return c1; } Game.java int nused = 0; for (int t : c) { if (t != 0) nused++; } int c1[] = new int[nused]; nused = 0; for (int t : c) { if (t != 0) c1[nused++] = t; } return c1; Game.java Representando o Puzzle • Podemos representar o puzzle como um arranjo de tamanho 9 × 9: private int puzzle[] = new int[9 * 9]; Nesse caso, como implementar os métodos: getTile(x, y) e ? setTile(x, y, value) Game.java Representando o Puzzle • Podemos representar o puzzle como um arranjo de tamanho 9 × 9: private int puzzle[] = new int[9 * 9]; private int getTile(int x, int y) { return puzzle[y * 9 + x]; } private void setTile(int x, int y, int value) { puzzle[y * 9 + x] = value; } Game.java O Sudoku Inicial • Precisamos inicializar o tabuleiro com um puzzle. • Este pode ser fácil, médio ou di‘cil. • Podemos representar esses puzzles como strings. private final String easyPuzzle = "360000000004230800000004200" + "070460003820000014500013020" + "001900000007048300000000045"; private final String mediumPuzzle = "650000070000506000014000005" + "007009000002314700000700800" + "500000630000201000030000097"; private final String hardPuzzle = "009000000080605020501078000" + "000000700706040102004000000" + "000720903090301080000000600"; Game.java O Sudoku Inicial • Por que representar as configurações iniciais de puzzles como Strings, e não como arranjos de inteiros? private final String easyPuzzle = "360000000004230800000004200" + "070460003820000014500013020" + "001900000007048300000000045"; private final String mediumPuzzle = "650000070000506000014000005" + "007009000002314700000700800" + "500000630000201000030000097"; private final String hardPuzzle = "009000000080605020501078000" + "000000700706040102004000000" + "000720903090301080000000600"; Game.java Obtendo um puzzle novo private int[] getPuzzle(int diff) { String puz; switch (diff) { case DIFFICULTY_HARD: puz = hardPuzzle; break; case DIFFICULTY_MEDIUM: puz = mediumPuzzle; break; case DIFFICULTY_EASY: default: puz = easyPuzzle; break; } return fromPuzzleString(puz); } Agora, escreva um método toPuzzleString, que converta um arranjo de inteiros, descrevendo um puzzle, em uma string. Game.java toPuzzleString staKc private String toPuzzleString(int[] puz) { StringBuilder buf = new StringBuilder(); for (int element : puz) { buf.append(element); } Faça o caminho inverso return buf.toString(); agora: escreva um } método que converta uma string em um arranjo de inteiros. Game.java fromPuzzleString staKc protected int[] fromPuzzleString(String string) { int[] puz = new int[string.length()]; for (int i = 0; i < puz.length; i++) { puz[i] = string.charAt(i) ‐ '0'; } return puz; } PuzzleView.java getTileString(x, y) float x = width / 2; float y = height / 2 ‐ (fm.ascent + fm.descent) / 2; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { canvas.drawText(this.game.getTileString(i, j), i * width + x, j * height + y, foreground); Implemente o método } getTileString, que retorna } uma string descrevendo o número em uma posição do tabuleiro, ou a string vazia se aquela posição estiver vaga. Game.java getTileString(x, y) protected String getTileString(int x, int y) { int v = getTile(x, y); if (v == 0) return ""; else return String.valueOf(v); } O Projeto de SoŽware ue Poder-se-ia dizer q i esse projeto possu ra. falhas de arquitetu Você conseguiria identificá-las? O Projeto de SoŽware ue Poder-se-ia dizer q i esse projeto possu ra. falhas de arquitetu Você conseguiria identificá-las? • A implementação do puzzle está muito misturada com a lógica do jogo. • Mas isso pode não ser um problema. • Reveja Game.java e discuta o projeto