diff --git a/app/src/main/java/legion/muyue/best2048/Game.java b/app/src/main/java/legion/muyue/best2048/Game.java index ec41638..2460c7c 100644 --- a/app/src/main/java/legion/muyue/best2048/Game.java +++ b/app/src/main/java/legion/muyue/best2048/Game.java @@ -1,6 +1,7 @@ package legion.muyue.best2048; import java.util.ArrayList; +import java.util.List; // Import explicite import java.util.Random; import android.util.Log; @@ -10,10 +11,13 @@ public class Game { private int[][] gameBoard; private Random random; private int score = 0; + private int highScore = 0; // Ajout variable high score + public Game() { gameBoard = new int[4][4]; random = new Random(); + loadHighScore(); // Charger au démarrage } public int getGameBoard(int x, int y) { @@ -25,20 +29,23 @@ public class Game { } public int getHighScore() { - return 0; // TODO: Implémentez la logique du meilleur score plus tard + return highScore; } - // La méthode printArray est toujours présente dans l'extrait de Game.java pour l'étape 8 - public void printArray() { - for (int[] row : gameBoard) { - String rowString = String.format("%6d%6d%6d%6d%n", row[0], row[1], row[2], row[3]); - Log.d("Game", rowString); - } - Log.d("Game", "\n"); + // Charger le meilleur score (stub) + private void loadHighScore() { + // highScore = sharedPreferences.getInt("high_score", 0); + } + + // Sauvegarder le meilleur score (stub) + private void saveHighScore() { + /* SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putInt("high_score", highScore); + editor.apply(); // ou editor.commit()*/ } public void addNewNumbers() { - ArrayList emptySpaces = new ArrayList<>(); + List emptySpaces = new ArrayList<>(); // Utilisation de List for (int x = 0; x < 4; x++) { for (int y = 0; y < 4; y++) { @@ -55,6 +62,8 @@ public class Game { int y = coordinates[1]; int newNumber; int randomNumber = random.nextInt(100); + + // Switch comme dans le snippet (même si if/else if était correct aussi) if (randomNumber < 85) { newNumber = 2; } else if (randomNumber < 95) { @@ -62,10 +71,12 @@ public class Game { } else { newNumber = 8; } + gameBoard[x][y] = newNumber; } } + // Les méthodes pushX appellent updateHighScore maintenant public void pushUp() { Log.d("Game", "Pushing Up"); @@ -95,6 +106,7 @@ public class Game { score += gameBoard[currentX - 1][y]; gameBoard[x][y] = 0; alreadyCombined[currentX - 1] = true; + updateHighScore(); // Appel après fusion } else { gameBoard[currentX][y] = value; if (currentX != x) { @@ -123,18 +135,21 @@ public class Game { if (currentX == 3) { gameBoard[3][y] = value; - if (currentX != x) + if(currentX != x) gameBoard[x][y] = 0; } else if (gameBoard[currentX + 1][y] != value) { gameBoard[currentX][y] = value; - if (currentX != x) + if(currentX != x) gameBoard[x][y] = 0; } else if (!alreadyCombined[currentX + 1]) { gameBoard[currentX + 1][y] *= 2; score += gameBoard[currentX + 1][y]; gameBoard[x][y] = 0; alreadyCombined[currentX + 1] = true; - } else { + updateHighScore(); // Appel après fusion + + } + else { gameBoard[currentX][y] = value; if (currentX != x) { gameBoard[x][y] = 0; @@ -173,6 +188,7 @@ public class Game { score += gameBoard[x][currentY - 1]; gameBoard[x][y] = 0; alreadyCombined[currentY - 1] = true; + updateHighScore(); // Appel après fusion } else { gameBoard[x][currentY] = value; if(currentY != y) @@ -211,7 +227,9 @@ public class Game { score += gameBoard[x][currentY + 1]; gameBoard[x][y] = 0; alreadyCombined[currentY + 1] = true; - } else { + updateHighScore(); // Appel après fusion + } + else{ gameBoard[x][currentY] = value; if (currentY != y) gameBoard[x][y] = 0; @@ -220,4 +238,21 @@ public class Game { } } } + + // Nouvelle méthode pour mettre à jour le high score + private void updateHighScore() { + if (score > highScore) { + highScore = score; + saveHighScore(); // Sauvegarder (même si le stub est vide) + } + } + + // La méthode printArray reste définie comme dans l'extrait précédent + public void printArray() { + for (int[] row : gameBoard) { + String rowString = String.format("%6d%6d%6d%6d%n", row[0], row[1], row[2], row[3]); + Log.d("Game", rowString); + } + Log.d("Game", "\n"); + } } \ No newline at end of file diff --git a/app/src/main/java/legion/muyue/best2048/MainActivity.java b/app/src/main/java/legion/muyue/best2048/MainActivity.java index 7a45c49..291aa1c 100644 --- a/app/src/main/java/legion/muyue/best2048/MainActivity.java +++ b/app/src/main/java/legion/muyue/best2048/MainActivity.java @@ -3,10 +3,13 @@ package legion.muyue.best2048; import android.os.Bundle; import android.util.TypedValue; import android.view.Gravity; +import android.view.animation.AnimationUtils; // Ajout pour Animation import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.gridlayout.widget.GridLayout; +import android.widget.Button; // Ajout pour Button + public class MainActivity extends AppCompatActivity { @@ -23,19 +26,31 @@ public class MainActivity extends AppCompatActivity { gameBoardLayout = findViewById(R.id.gameBoard); scoreTextView = findViewById(R.id.scoreLabel); highScoreTextView = findViewById(R.id.highScoreLabel); + Button restartButton = findViewById(R.id.restartButton); // Initialisation + Button statsButton = findViewById(R.id.statsButton); // Initialisation + Button menuButton = findViewById(R.id.menuButton); // Initialisation + Button multiplayerButton = findViewById(R.id.multiplayerButton); // Initialisation game = new Game(); initGameBoardLayout(); - game.addNewNumbers(); - game.addNewNumbers(); - game.addNewNumbers(); - updateUI(); + // Initialisation du jeu et de l'UI via restartGame + restartGame(); // Ajout des listeners de swipe setupSwipeListener(); + + //Listeners des boutons + multiplayerButton.setOnClickListener(v -> { + v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press)); + showMultiplayerScreen(); + }); + restartButton.setOnClickListener(v -> restartGame()); // Lambda pour restart + statsButton.setOnClickListener(v -> showStats()); // Lambda pour stats + menuButton.setOnClickListener(v -> showMenu()); // Lambda pour menu } + private void initGameBoardLayout() { gameBoardLayout.removeAllViews(); gameBoardLayout.setColumnCount(4); @@ -50,105 +65,200 @@ public class MainActivity extends AppCompatActivity { TextView tileTextView = new TextView(this); int value = game.getGameBoard(x, y); - // Choisir le bon drawable en fonction de la valeur - int drawableId; - - switch (value) { - case 2: - drawableId = R.drawable.tile2; - break; - case 4: - drawableId = R.drawable.tile4; - break; - case 8: - drawableId = R.drawable.tile8; - break; - case 16: - drawableId = R.drawable.tile16; - break; - case 32: - drawableId = R.drawable.tile32; - break; - case 64: - drawableId = R.drawable.tile64; - break; - case 128: - drawableId = R.drawable.tile128; - break; - case 256: - drawableId = R.drawable.tile256; - break; - case 512: - drawableId = R.drawable.tile512; - break; - case 1024: - drawableId = R.drawable.tile1024; - break; - case 2048: - drawableId = R.drawable.tile2048; - break; - default: - drawableId = R.drawable.tile_empty; - break; - } - - tileTextView.setBackground(ContextCompat.getDrawable(this, drawableId)); - - // Afficher le texte uniquement si la valeur est > 0 - if (value > 0) { - tileTextView.setText(String.valueOf(value)); - // Adapte la taille du texte en fonction de la valeur. - tileTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, value < 128 ? 24 : (value < 1024 ? 20 : 16)); - - tileTextView.setTextColor(ContextCompat.getColor(this, R.color.text_tile_low)); - } - tileTextView.setGravity(Gravity.CENTER); + // Utilisation d'une méthode pour définir l'arrière-plan et le texte + setTileAppearance(tileTextView, value); GridLayout.LayoutParams params = new GridLayout.LayoutParams(); params.width = 0; params.height = 0; params.rowSpec = GridLayout.spec(x, 1f); params.columnSpec = GridLayout.spec(y, 1f); - params.setMargins(10, 10, 10, 10); // Marges codées en dur selon l'extrait + // Utilisation des dimensions pour les marges + params.setMargins( + (int) getResources().getDimension(R.dimen.tile_margin), + (int) getResources().getDimension(R.dimen.tile_margin), + (int) getResources().getDimension(R.dimen.tile_margin), + (int) getResources().getDimension(R.dimen.tile_margin) + ); tileTextView.setLayoutParams(params); gameBoardLayout.addView(tileTextView); } } + // Met à jour les TextViews du score et du high score en utilisant getString + scoreTextView.setText(getString(R.string.score, game.getScore())); + highScoreTextView.setText(getString(R.string.high_score, game.getHighScore())); - scoreTextView.setText("Score:\n" + game.getScore()); // Concaténation simple selon l'extrait - highScoreTextView.setText("High Score:\n" + game.getHighScore()); // Concaténation simple selon l'extrait } + // Méthode refactorisée pour l'apparence des tuiles + private void setTileAppearance(TextView tile, int value) { + // int drawableId; // N'est plus utilisé directement + int textColorId; + int textSizeId; + + // Utilisation d'un switch pour déterminer la couleur de fond, texte et taille. + switch (value) { + case 2: + tile.setBackgroundResource(R.drawable.tile_background); // Utilise le drawable générique + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_2_color)); // Applique la teinte + textColorId = R.color.text_tile_low; + textSizeId = R.dimen.text_size_tile_small; + break; + case 4: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_4_color)); + textColorId = R.color.text_tile_low; + textSizeId = R.dimen.text_size_tile_small; + break; + case 8: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_8_color)); + textColorId = R.color.text_tile_low; // Couleur texte change à partir de 8 dans le code final, mais pas dans ce snippet + textSizeId = R.dimen.text_size_tile_small; + break; + case 16: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_16_color)); + textColorId = R.color.text_tile_low; // Devrait être text_tile_high selon la logique standard 2048 + textSizeId = R.dimen.text_size_tile_small; + break; + case 32: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_32_color)); + textColorId = R.color.text_tile_low; // Devrait être text_tile_high + textSizeId = R.dimen.text_size_tile_small; + break; + case 64: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_64_color)); + textColorId = R.color.text_tile_low; // Devrait être text_tile_high + textSizeId = R.dimen.text_size_tile_small; + break; + case 128: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_128_color)); + textColorId = R.color.text_tile_high; // Correct ici + textSizeId = R.dimen.text_size_tile_medium; // Taille change + break; + case 256: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_256_color)); + textColorId = R.color.text_tile_high; + textSizeId = R.dimen.text_size_tile_medium; + break; + case 512: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_512_color)); + textColorId = R.color.text_tile_high; + textSizeId = R.dimen.text_size_tile_medium; + break; + case 1024: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_1024_color)); + textColorId = R.color.text_tile_high; + textSizeId = R.dimen.text_size_tile_large; // Taille change + break; + case 2048: + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_2048_color)); + textColorId = R.color.text_tile_high; + textSizeId = R.dimen.text_size_tile_large; + break; + default: // Tuile vide ou > 2048 + tile.setBackgroundResource(R.drawable.tile_background); + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_empty_color)); + textColorId = android.R.color.transparent; // Pas de texte + textSizeId = R.dimen.text_size_tile_small; // Taille par défaut + // Gérer les tuiles > 2048 si nécessaire (non montré dans ce snippet) + if (value > 2048) { + tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_super_color)); + textColorId = R.color.text_tile_high; + textSizeId = R.dimen.text_size_tile_large; + } + break; + } + + if (value > 0) { + tile.setText(String.valueOf(value)); + tile.setTextColor(ContextCompat.getColor(this, textColorId)); + // Utilise la dimension pour la taille du texte + tile.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(textSizeId)); + } else { + tile.setText(""); // Efface le texte pour les tuiles vides + } + tile.setGravity(Gravity.CENTER); + } + + private void setupSwipeListener() { - gameBoardLayout.setOnTouchListener(new OnSwipeTouchListener(MainActivity.this) { + // Utilise l'interface SwipeListener + gameBoardLayout.setOnTouchListener(new OnSwipeTouchListener(MainActivity.this, new OnSwipeTouchListener.SwipeListener() { @Override public void onSwipeTop() { - game.pushUp(); - game.addNewNumbers(); - updateUI(); + handleSwipe(Direction.UP); } @Override public void onSwipeBottom() { - game.pushDown(); - game.addNewNumbers(); - updateUI(); + handleSwipe(Direction.DOWN); } @Override public void onSwipeLeft() { - game.pushLeft(); - game.addNewNumbers(); - updateUI(); + handleSwipe(Direction.LEFT); } @Override public void onSwipeRight() { - game.pushRight(); - game.addNewNumbers(); - updateUI(); + handleSwipe(Direction.RIGHT); } - }); + })); } + + // Méthode pour gérer les swipes via l'enum + private void handleSwipe(Direction direction) { + switch (direction) { + case UP: + game.pushUp(); + break; + case DOWN: + game.pushDown(); + break; + case LEFT: + game.pushLeft(); + break; + case RIGHT: + game.pushRight(); + break; + } + game.addNewNumbers(); + updateUI(); + } + + // Méthode pour redémarrer le jeu + private void restartGame() { + game = new Game(); // Crée une nouvelle instance de Game + game.addNewNumbers(); // Ajoute les tuiles initiales + game.addNewNumbers(); + updateUI(); // Met à jour l'interface utilisateur + } + + // Stubs pour les autres boutons + private void showStats() { + //A faire + } + private void showMenu() { + //A faire + } + + private void showMultiplayerScreen() { + //A faire + } + + // Enum pour les directions + private enum Direction { + UP, DOWN, LEFT, RIGHT + } + } \ No newline at end of file diff --git a/app/src/main/java/legion/muyue/best2048/OnSwipeTouchListener.java b/app/src/main/java/legion/muyue/best2048/OnSwipeTouchListener.java index 22f8171..001f050 100644 --- a/app/src/main/java/legion/muyue/best2048/OnSwipeTouchListener.java +++ b/app/src/main/java/legion/muyue/best2048/OnSwipeTouchListener.java @@ -8,9 +8,20 @@ import android.view.View; public class OnSwipeTouchListener implements View.OnTouchListener { private final GestureDetector gestureDetector; + private final SwipeListener listener; // Ajout de l'interface listener - public OnSwipeTouchListener(Context ctx) { + // Interface pour les événements de swipe + public interface SwipeListener { + void onSwipeTop(); + void onSwipeBottom(); + void onSwipeLeft(); + void onSwipeRight(); + } + + // Constructeur modifié pour accepter le listener + public OnSwipeTouchListener(Context ctx, SwipeListener listener) { gestureDetector = new GestureDetector(ctx, new GestureListener()); + this.listener = listener; } @Override @@ -37,17 +48,17 @@ public class OnSwipeTouchListener implements View.OnTouchListener { if (Math.abs(diffX) > Math.abs(diffY)) { if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { if (diffX > 0) { - onSwipeRight(); + listener.onSwipeRight(); // Appel via listener } else { - onSwipeLeft(); + listener.onSwipeLeft(); // Appel via listener } result = true; } } else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) { if (diffY > 0) { - onSwipeBottom(); + listener.onSwipeBottom(); // Appel via listener } else { - onSwipeTop(); + listener.onSwipeTop(); // Appel via listener } result = true; } @@ -57,16 +68,5 @@ public class OnSwipeTouchListener implements View.OnTouchListener { return result; } } - - public void onSwipeRight() { - } - - public void onSwipeLeft() { - } - - public void onSwipeTop() { - } - - public void onSwipeBottom() { - } + // Les méthodes locales vides ne sont plus nécessaires car on utilise l'interface } \ No newline at end of file diff --git a/app/src/main/res/anim/button_press.xml b/app/src/main/res/anim/button_press.xml new file mode 100644 index 0000000..2c62956 --- /dev/null +++ b/app/src/main/res/anim/button_press.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/button_release.xml b/app/src/main/res/anim/button_release.xml new file mode 100644 index 0000000..f796d29 --- /dev/null +++ b/app/src/main/res/anim/button_release.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_multiplayer_background.xml b/app/src/main/res/drawable/button_multiplayer_background.xml new file mode 100644 index 0000000..20d0310 --- /dev/null +++ b/app/src/main/res/drawable/button_multiplayer_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tile1024.xml b/app/src/main/res/drawable/tile1024.xml deleted file mode 100644 index 0ef9cf2..0000000 --- a/app/src/main/res/drawable/tile1024.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile128.xml b/app/src/main/res/drawable/tile128.xml deleted file mode 100644 index 21a69ac..0000000 --- a/app/src/main/res/drawable/tile128.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile16.xml b/app/src/main/res/drawable/tile16.xml deleted file mode 100644 index 51eb822..0000000 --- a/app/src/main/res/drawable/tile16.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile2.xml b/app/src/main/res/drawable/tile2.xml deleted file mode 100644 index 2db5f9c..0000000 --- a/app/src/main/res/drawable/tile2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile2048.xml b/app/src/main/res/drawable/tile2048.xml deleted file mode 100644 index 9cbf3ff..0000000 --- a/app/src/main/res/drawable/tile2048.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile256.xml b/app/src/main/res/drawable/tile256.xml deleted file mode 100644 index 53142b2..0000000 --- a/app/src/main/res/drawable/tile256.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile32.xml b/app/src/main/res/drawable/tile32.xml deleted file mode 100644 index f0fb515..0000000 --- a/app/src/main/res/drawable/tile32.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile4.xml b/app/src/main/res/drawable/tile4.xml deleted file mode 100644 index 5ab4fe5..0000000 --- a/app/src/main/res/drawable/tile4.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile512.xml b/app/src/main/res/drawable/tile512.xml deleted file mode 100644 index 4e3d917..0000000 --- a/app/src/main/res/drawable/tile512.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile64.xml b/app/src/main/res/drawable/tile64.xml deleted file mode 100644 index 5eac67c..0000000 --- a/app/src/main/res/drawable/tile64.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile8.xml b/app/src/main/res/drawable/tile8.xml deleted file mode 100644 index feb04e9..0000000 --- a/app/src/main/res/drawable/tile8.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tile_background.xml b/app/src/main/res/drawable/tile_background.xml new file mode 100644 index 0000000..c1763d7 --- /dev/null +++ b/app/src/main/res/drawable/tile_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tile_empty.xml b/app/src/main/res/drawable/tile_empty.xml deleted file mode 100644 index 72aded9..0000000 --- a/app/src/main/res/drawable/tile_empty.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9792d39..39086bd 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#FCFAEE" + android:background="@color/background_color" tools:context=".MainActivity"> @@ -20,20 +20,20 @@ - + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="@+id/gameLabel"> - - - - + style="@style/ScoreLabelStyle" + android:text="@string/score" /> @@ -85,11 +65,11 @@ android:id="@+id/gameBoardContainer" android:layout_width="0dp" android:layout_height="0dp" - android:layout_margin="16dp" + android:layout_margin="@dimen/padding_general" app:cardBackgroundColor="@android:color/transparent" - app:cardCornerRadius="6dp" + app:cardCornerRadius="@dimen/corner_radius" app:cardElevation="0dp" - app:layout_constraintBottom_toTopOf="@+id/restartButton" + app:layout_constraintBottom_toTopOf="@+id/multiplayerButton" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -99,60 +79,33 @@ android:id="@+id/gameBoard" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#A3937D" - android:padding="4dp" + android:background="@color/game_board_background" + android:padding="@dimen/padding_game_board" app:columnCount="4" - app:rowCount="4"> - - + app:rowCount="4" />