From 8661fd8a8011ae303d8369216f1e6b282481deff Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Fri, 28 Mar 2025 11:57:21 -0400 Subject: [PATCH 1/6] Disable diagonal inputs and prevent accidental hard drops. Block back swipe gesture to prevent accidental app exits. --- app/src/main/AndroidManifest.xml | 7 +- app/src/main/java/com/mintris/MainActivity.kt | 84 +++++++++++++++++++ .../main/java/com/mintris/game/GameView.kt | 21 +++-- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aaa37f3..924b03a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + android:theme="@style/Theme.Mintris.NoActionBar" + android:immersive="true" + android:resizeableActivity="false" + android:excludeFromRecents="false"> diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index e403d6c..2f39436 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -32,6 +32,8 @@ import java.util.* import android.graphics.Color import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import android.graphics.Rect +import android.view.KeyEvent class MainActivity : AppCompatActivity() { @@ -73,6 +75,9 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + // Disable Android back gesture to prevent accidental app exits + disableAndroidBackGesture() + // Initialize game components gameBoard = GameBoard() gameHaptics = GameHaptics(this) @@ -646,4 +651,83 @@ class MainActivity : AppCompatActivity() { else -> Color.WHITE } } + + /** + * Disables the Android system back gesture to prevent accidental exits + */ + private fun disableAndroidBackGesture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Set the entire window to be excluded from the system gesture areas + window.decorView.post { + // Create a list of rectangles representing the edges of the screen to exclude from system gestures + val gestureInsets = window.decorView.rootWindowInsets?.systemGestureInsets + if (gestureInsets != null) { + val leftEdge = Rect(0, 0, 50, window.decorView.height) + val rightEdge = Rect(window.decorView.width - 50, 0, window.decorView.width, window.decorView.height) + val bottomEdge = Rect(0, window.decorView.height - 50, window.decorView.width, window.decorView.height) + + window.decorView.systemGestureExclusionRects = listOf(leftEdge, rightEdge, bottomEdge) + } + } + } + + // Add an on back pressed callback to handle back button/gesture + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + onBackPressedDispatcher.addCallback(this, object : androidx.activity.OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + // If we're playing the game, handle it as a pause action instead of exiting + if (gameView.visibility == View.VISIBLE && !gameView.isPaused && !gameView.isGameOver()) { + gameView.pause() + gameMusic.pause() + showPauseMenu() + binding.pauseStartButton.visibility = View.GONE + binding.resumeButton.visibility = View.VISIBLE + } else if (binding.pauseContainer.visibility == View.VISIBLE) { + // If pause menu is showing, handle as a resume + resumeGame() + } else if (binding.gameOverContainer.visibility == View.VISIBLE) { + // If game over is showing, go back to title + hideGameOver() + showTitleScreen() + } else if (titleScreen.visibility == View.VISIBLE) { + // If title screen is showing, allow normal back behavior (exit app) + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + }) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // For Android 11 (R) to Android 12 (S), use the WindowInsetsController to disable gestures + window.insetsController?.systemBarsBehavior = + android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } + + /** + * Completely block the hardware back button during gameplay + */ + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + // If back button is pressed + if (keyCode == KeyEvent.KEYCODE_BACK) { + // Handle back button press as a pause action during gameplay + if (gameView.visibility == View.VISIBLE && !gameView.isPaused && !gameView.isGameOver()) { + gameView.pause() + gameMusic.pause() + showPauseMenu() + binding.pauseStartButton.visibility = View.GONE + binding.resumeButton.visibility = View.VISIBLE + return true // Consume the event + } else if (binding.pauseContainer.visibility == View.VISIBLE) { + // If pause menu is showing, handle as a resume + resumeGame() + return true // Consume the event + } else if (binding.gameOverContainer.visibility == View.VISIBLE) { + // If game over is showing, go back to title + hideGameOver() + showTitleScreen() + return true // Consume the event + } + } + return super.onKeyDown(keyCode, event) + } } \ No newline at end of file diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt index ce82647..95332db 100644 --- a/app/src/main/java/com/mintris/game/GameView.kt +++ b/app/src/main/java/com/mintris/game/GameView.kt @@ -39,7 +39,8 @@ class GameView @JvmOverloads constructor( // Game state private var isRunning = false - private var isPaused = false + var isPaused = false // Changed from private to public to allow access from MainActivity + private var score = 0 // Callbacks var onNextPieceChanged: (() -> Unit)? = null @@ -128,14 +129,16 @@ class GameView @JvmOverloads constructor( private var lastTapTime = 0L private var lastRotationTime = 0L private var lastMoveTime = 0L - private var minSwipeVelocity = 800 // Minimum velocity for swipe to be considered a hard drop + private var minSwipeVelocity = 1200 // Increased from 800 to require more deliberate swipes private val maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels) private val minTapTime = 100L // Minimum time for a tap (in milliseconds) private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds) private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds) private var lockedDirection: Direction? = null // Track the locked movement direction private val minMovementThreshold = 0.75f // Minimum movement threshold relative to block size - private val directionLockThreshold = 1.5f // Threshold for direction lock relative to block size + private val directionLockThreshold = 2.5f // Increased from 1.5f to make direction locking more aggressive + private val isStrictDirectionLock = true // Enable strict direction locking to prevent diagonal inputs + private val minHardDropDistance = 1.5f // Minimum distance (in blocks) for hard drop gesture private enum class Direction { HORIZONTAL, VERTICAL @@ -612,12 +615,14 @@ class GameView @JvmOverloads constructor( // Check if movement exceeds threshold if (absDeltaX > blockSize * minMovementThreshold || absDeltaY > blockSize * minMovementThreshold) { - // Determine dominant direction + // Determine dominant direction with stricter criteria if (absDeltaX > absDeltaY * directionLockThreshold) { lockedDirection = Direction.HORIZONTAL } else if (absDeltaY > absDeltaX * directionLockThreshold) { lockedDirection = Direction.VERTICAL } + // If strict direction lock is enabled and we couldn't determine a clear direction, don't set one + // This prevents diagonal movements from being recognized } } @@ -661,8 +666,12 @@ class GameView @JvmOverloads constructor( val deltaY = event.y - startY val deltaX = event.x - startX - // If the movement was fast and downward, treat as hard drop - if (moveTime > 0 && deltaY > blockSize * 0.5f && (deltaY / moveTime) * 1000 > minSwipeVelocity) { + // Only allow hard drops with a deliberate downward swipe + // Requires: predominantly vertical movement, minimum distance, and minimum velocity + if (moveTime > 0 && + deltaY > blockSize * minHardDropDistance && // Require longer swipe for hard drop + (deltaY / moveTime) * 1000 > minSwipeVelocity && + abs(deltaX) < abs(deltaY) * 0.3f) { // Require more purely vertical movement (reduced from 0.5f to 0.3f) gameBoard.hardDrop() invalidate() } else if (moveTime < minTapTime && From 9ab9b534071386da15506105f7bfd4810edbdcb5 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Fri, 28 Mar 2025 12:33:42 -0400 Subject: [PATCH 2/6] Fix menu shifting issue and code cleanup. Fix menu shifting by adding fillViewport and padding to ScrollView. Added missing getLastClearedLines method. Improved code quality with proper logging constants and removed unused imports. --- app/src/main/java/com/mintris/MainActivity.kt | 25 +++--- .../main/java/com/mintris/game/GameHaptics.kt | 87 +++++++++++-------- .../main/java/com/mintris/game/GameView.kt | 38 ++++---- .../main/java/com/mintris/model/GameBoard.kt | 36 ++++++-- app/src/main/res/layout/activity_main.xml | 6 +- 5 files changed, 119 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index 2f39436..95ef64c 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -1,26 +1,18 @@ package com.mintris -import android.animation.ObjectAnimator -import android.animation.ValueAnimator import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import android.os.VibrationEffect import android.os.Vibrator -import android.os.VibratorManager import android.view.View -import android.widget.Button -import android.widget.LinearLayout -import android.widget.TextView +import android.view.HapticFeedbackConstants import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import com.mintris.databinding.ActivityMainBinding import com.mintris.game.GameHaptics import com.mintris.game.GameView -import com.mintris.game.NextPieceView import com.mintris.game.TitleScreen -import android.view.HapticFeedbackConstants import com.mintris.model.GameBoard import com.mintris.audio.GameMusic import com.mintris.model.HighScoreManager @@ -33,10 +25,15 @@ import android.graphics.Color import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import android.graphics.Rect +import android.util.Log import android.view.KeyEvent class MainActivity : AppCompatActivity() { + companion object { + private const val TAG = "MainActivity" + } + // UI components private lateinit var binding: ActivityMainBinding private lateinit var gameView: GameView @@ -165,18 +162,18 @@ class MainActivity : AppCompatActivity() { } gameView.onLineClear = { lineCount -> - android.util.Log.d("MainActivity", "Received line clear callback: $lineCount lines") + Log.d(TAG, "Received line clear callback: $lineCount lines") // Use enhanced haptic feedback for line clears if (isSoundEnabled) { - android.util.Log.d("MainActivity", "Sound is enabled, triggering haptic feedback") + Log.d(TAG, "Sound is enabled, triggering haptic feedback") try { gameHaptics.vibrateForLineClear(lineCount) - android.util.Log.d("MainActivity", "Haptic feedback triggered successfully") + Log.d(TAG, "Haptic feedback triggered successfully") } catch (e: Exception) { - android.util.Log.e("MainActivity", "Error triggering haptic feedback", e) + Log.e(TAG, "Error triggering haptic feedback", e) } } else { - android.util.Log.d("MainActivity", "Sound is disabled, skipping haptic feedback") + Log.d(TAG, "Sound is disabled, skipping haptic feedback") } // Record line clear in stats statsManager.recordLineClear(lineCount) diff --git a/app/src/main/java/com/mintris/game/GameHaptics.kt b/app/src/main/java/com/mintris/game/GameHaptics.kt index 9dc1e87..44e5011 100644 --- a/app/src/main/java/com/mintris/game/GameHaptics.kt +++ b/app/src/main/java/com/mintris/game/GameHaptics.kt @@ -7,15 +7,66 @@ import android.os.Vibrator import android.os.VibratorManager import android.view.HapticFeedbackConstants import android.view.View +import android.util.Log +/** + * Handles haptic feedback for game events + */ class GameHaptics(private val context: Context) { - private val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + + companion object { + private const val TAG = "GameHaptics" + } + + // Vibrator service + private val vibrator: Vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager vibratorManager.defaultVibrator } else { @Suppress("DEPRECATION") context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator } + + // Vibrate for line clear (more intense for more lines) + fun vibrateForLineClear(lineCount: Int) { + Log.d(TAG, "Attempting to vibrate for $lineCount lines") + + // Only proceed if the device has a vibrator and it's available + if (!vibrator.hasVibrator()) return + + // Scale duration and amplitude based on line count + // More lines = longer and stronger vibration + val duration = when(lineCount) { + 1 -> 50L // Single line: short vibration + 2 -> 80L // Double line: slightly longer + 3 -> 120L // Triple line: even longer + 4 -> 200L // Tetris: longest vibration + else -> 50L + } + + val amplitude = when(lineCount) { + 1 -> 80 // Single line: mild vibration (80/255) + 2 -> 120 // Double line: medium vibration (120/255) + 3 -> 180 // Triple line: strong vibration (180/255) + 4 -> 255 // Tetris: maximum vibration (255/255) + else -> 80 + } + + Log.d(TAG, "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude") + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(duration, amplitude)) + Log.d(TAG, "Vibration triggered successfully") + } else { + @Suppress("DEPRECATION") + vibrator.vibrate(duration) + Log.w(TAG, "Device does not support vibration effects (Android < 8.0)") + } + } catch (e: Exception) { + Log.e(TAG, "Error triggering vibration", e) + } + } fun performHapticFeedback(view: View, feedbackType: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -26,40 +77,6 @@ class GameHaptics(private val context: Context) { } } - fun vibrateForLineClear(lineCount: Int) { - android.util.Log.d("GameHaptics", "Attempting to vibrate for $lineCount lines") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val duration = when (lineCount) { - 4 -> 200L // Tetris - doubled from 100L - 3 -> 160L // Triples - doubled from 80L - 2 -> 120L // Doubles - doubled from 60L - 1 -> 80L // Singles - doubled from 40L - else -> 0L - } - - val amplitude = when (lineCount) { - 4 -> 255 // Full amplitude for Tetris - 3 -> 230 // 90% amplitude for triples - 2 -> 180 // 70% amplitude for doubles - 1 -> 128 // 50% amplitude for singles - else -> 0 - } - - android.util.Log.d("GameHaptics", "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude") - if (duration > 0 && amplitude > 0) { - try { - val vibrationEffect = VibrationEffect.createOneShot(duration, amplitude) - vibrator.vibrate(vibrationEffect) - android.util.Log.d("GameHaptics", "Vibration triggered successfully") - } catch (e: Exception) { - android.util.Log.e("GameHaptics", "Error triggering vibration", e) - } - } - } else { - android.util.Log.w("GameHaptics", "Device does not support vibration effects (Android < 8.0)") - } - } - fun vibrateForPieceLock() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val vibrationEffect = VibrationEffect.createOneShot(50L, VibrationEffect.DEFAULT_AMPLITUDE) diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt index 95332db..95d4822 100644 --- a/app/src/main/java/com/mintris/game/GameView.kt +++ b/app/src/main/java/com/mintris/game/GameView.kt @@ -12,12 +12,12 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.util.AttributeSet +import android.util.Log import android.view.MotionEvent import android.view.View import android.view.animation.LinearInterpolator -import android.view.WindowManager -import android.view.Display import android.hardware.display.DisplayManager +import android.view.Display import com.mintris.model.GameBoard import com.mintris.model.Tetromino import com.mintris.model.TetrominoType @@ -33,6 +33,10 @@ class GameView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { + companion object { + private const val TAG = "GameView" + } + // Game board model private var gameBoard = GameBoard() private var gameHaptics: GameHaptics? = null @@ -165,17 +169,17 @@ class GameView @JvmOverloads constructor( gameBoard.onPieceMove = { onPieceMove?.invoke() } gameBoard.onPieceLock = { onPieceLock?.invoke() } gameBoard.onLineClear = { lineCount, clearedLines -> - android.util.Log.d("GameView", "Received line clear from GameBoard: $lineCount lines") + Log.d(TAG, "Received line clear from GameBoard: $lineCount lines") try { onLineClear?.invoke(lineCount) // Use the lines that were cleared directly linesToPulse.clear() linesToPulse.addAll(clearedLines) - android.util.Log.d("GameView", "Found ${linesToPulse.size} lines to pulse") + Log.d(TAG, "Found ${linesToPulse.size} lines to pulse") startPulseAnimation(lineCount) - android.util.Log.d("GameView", "Forwarded line clear callback") + Log.d(TAG, "Forwarded line clear callback") } catch (e: Exception) { - android.util.Log.e("GameView", "Error forwarding line clear callback", e) + Log.e(TAG, "Error forwarding line clear callback", e) } } @@ -184,7 +188,11 @@ class GameView @JvmOverloads constructor( // Set better frame rate using modern APIs val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager - val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY) + val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + displayManager.getDisplay(Display.DEFAULT_DISPLAY) + } else { + displayManager.displays.firstOrNull() + } display?.let { disp -> val refreshRate = disp.refreshRate // Set game loop interval based on refresh rate, but don't go faster than the base interval @@ -289,7 +297,7 @@ class GameView @JvmOverloads constructor( val totalHeight = blockSize * verticalBlocks // Log dimensions for debugging - android.util.Log.d("GameView", "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight") + Log.d(TAG, "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight") } override fun onDraw(canvas: Canvas) { @@ -739,17 +747,17 @@ class GameView @JvmOverloads constructor( gameBoard.onPieceMove = { onPieceMove?.invoke() } gameBoard.onPieceLock = { onPieceLock?.invoke() } gameBoard.onLineClear = { lineCount, clearedLines -> - android.util.Log.d("GameView", "Received line clear from GameBoard: $lineCount lines") + Log.d(TAG, "Received line clear from GameBoard: $lineCount lines") try { onLineClear?.invoke(lineCount) // Use the lines that were cleared directly linesToPulse.clear() linesToPulse.addAll(clearedLines) - android.util.Log.d("GameView", "Found ${linesToPulse.size} lines to pulse") + Log.d(TAG, "Found ${linesToPulse.size} lines to pulse") startPulseAnimation(lineCount) - android.util.Log.d("GameView", "Forwarded line clear callback") + Log.d(TAG, "Forwarded line clear callback") } catch (e: Exception) { - android.util.Log.e("GameView", "Error forwarding line clear callback", e) + Log.e(TAG, "Error forwarding line clear callback", e) } } @@ -785,7 +793,7 @@ class GameView @JvmOverloads constructor( * Start the pulse animation for line clear */ private fun startPulseAnimation(lineCount: Int) { - android.util.Log.d("GameView", "Starting pulse animation for $lineCount lines") + Log.d(TAG, "Starting pulse animation for $lineCount lines") // Cancel any existing animation pulseAnimator?.cancel() @@ -804,7 +812,7 @@ class GameView @JvmOverloads constructor( pulseAlpha = animation.animatedValue as Float isPulsing = true invalidate() - android.util.Log.d("GameView", "Pulse animation update: alpha = $pulseAlpha") + Log.d(TAG, "Pulse animation update: alpha = $pulseAlpha") } addListener(object : android.animation.AnimatorListenerAdapter() { override fun onAnimationEnd(animation: android.animation.Animator) { @@ -812,7 +820,7 @@ class GameView @JvmOverloads constructor( pulseAlpha = 0f linesToPulse.clear() invalidate() - android.util.Log.d("GameView", "Pulse animation ended") + Log.d(TAG, "Pulse animation ended") } }) } diff --git a/app/src/main/java/com/mintris/model/GameBoard.kt b/app/src/main/java/com/mintris/model/GameBoard.kt index 5203109..b72921b 100644 --- a/app/src/main/java/com/mintris/model/GameBoard.kt +++ b/app/src/main/java/com/mintris/model/GameBoard.kt @@ -1,6 +1,6 @@ package com.mintris.model -import kotlin.random.Random +import android.util.Log /** * Represents the game board (grid) and manages game state @@ -9,6 +9,10 @@ class GameBoard( val width: Int = 10, val height: Int = 20 ) { + companion object { + private const val TAG = "GameBoard" + } + // Board grid to track locked pieces // True = occupied, False = empty private val grid = Array(height) { BooleanArray(width) { false } } @@ -55,6 +59,9 @@ class GameBoard( var onNextPieceChanged: (() -> Unit)? = null var onLineClear: ((Int, List) -> Unit)? = null + // Store the last cleared lines + private val lastClearedLines = mutableListOf() + init { spawnNextPiece() spawnPiece() @@ -66,7 +73,7 @@ class GameBoard( private fun spawnNextPiece() { // If bag is empty, refill it with all piece types if (bag.isEmpty()) { - bag.addAll(TetrominoType.values()) + bag.addAll(TetrominoType.entries.toTypedArray()) bag.shuffle() } @@ -326,18 +333,26 @@ class GameBoard( y-- } + // Store the last cleared lines + lastClearedLines.clear() + lastClearedLines.addAll(linesToClear) + // If lines were cleared, calculate score in background and trigger callback if (shiftAmount > 0) { - android.util.Log.d("GameBoard", "Lines cleared: $shiftAmount") + // Log line clear + Log.d(TAG, "Lines cleared: $shiftAmount") + // Trigger line clear callback on main thread with the lines that were cleared val mainHandler = android.os.Handler(android.os.Looper.getMainLooper()) mainHandler.post { - android.util.Log.d("GameBoard", "Triggering onLineClear callback with $shiftAmount lines") + // Call the line clear callback with the cleared line count try { - onLineClear?.invoke(shiftAmount, linesToClear) // Pass the lines that were cleared - android.util.Log.d("GameBoard", "onLineClear callback completed successfully") + Log.d(TAG, "Triggering onLineClear callback with $shiftAmount lines") + val clearedLines = getLastClearedLines() + onLineClear?.invoke(shiftAmount, clearedLines) + Log.d(TAG, "onLineClear callback completed successfully") } catch (e: Exception) { - android.util.Log.e("GameBoard", "Error in onLineClear callback", e) + Log.e(TAG, "Error in onLineClear callback", e) } } @@ -582,4 +597,11 @@ class GameBoard( * Get the current combo count */ fun getCombo(): Int = combo + + /** + * Get the list of lines that were most recently cleared + */ + private fun getLastClearedLines(): List { + return lastClearedLines.toList() + } } \ 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 371cb8c..939dc48 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -282,13 +282,15 @@ + android:layout_weight="1" + android:fillViewport="true"> + android:gravity="center" + android:paddingTop="16dp">