{"id":17059,"date":"2025-10-30T20:41:58","date_gmt":"2025-10-30T18:41:58","guid":{"rendered":"https:\/\/www.beseit.net\/?page_id=17059"},"modified":"2025-11-03T11:28:13","modified_gmt":"2025-11-03T09:28:13","slug":"crear-el-projecte-a-android-studio","status":"publish","type":"page","link":"https:\/\/www.beseit.net\/?page_id=17059","title":{"rendered":"Crear el projecte a Android Studio (codi)"},"content":{"rendered":"\n<p>Perfecte \u2014 et dono una guia pas a pas (en catal\u00e0) per crear una app <strong>Android (no Compose)<\/strong> anomenada <strong><code>tts_cat<\/code><\/strong> que fa servir <strong>TextToSpeech<\/strong>. La UI ser\u00e0 basada en <strong>XML<\/strong> i tindr\u00e0:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Un contenidor (usar\u00e9 <code>ConstraintLayout<\/code>) amb un camp de text (<code>EditText<\/code>) amb text precarregat en catal\u00e0.<\/li>\n\n\n\n<li>2 botons: <strong>Parlar<\/strong> i <strong>Parar<\/strong>.<\/li>\n\n\n\n<li>L\u00f2gica a <code>MainActivity.kt<\/code> per inicialitzar TTS, parlar el text i aturar la parla.<\/li>\n\n\n\n<li>Maneig de la vida del TTS (alliberament a <code>onDestroy<\/code>) i comprovaci\u00f3 si falta la veu catalana (i acci\u00f3 per instal\u00b7lar dades de TTS).<\/li>\n<\/ul>\n\n\n\n<p>Segueix aquests passos i copia\/enganxa els fitxers.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1) Crear el projecte a Android Studio<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>File &gt; New &gt; New Project<\/code>.<\/li>\n\n\n\n<li>Escull<mark style=\"background-color:rgba(0, 0, 0, 0);color:#ed1313\" class=\"has-inline-color\"> <strong>Empty Activity<\/strong> <\/mark>(no Compose).<\/li>\n\n\n\n<li>Nom: <code>tts_cat<\/code><\/li>\n\n\n\n<li>Language: <strong>Kotlin<\/strong><\/li>\n\n\n\n<li>Minimum SDK: API 21+ (recomanat)<\/li>\n\n\n\n<li>Finalitza i deixa que Android Studio generi el projecte.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2) build.gradle (Module: app)<\/h2>\n\n\n\n<p>Assegura\u2019t que tens depend\u00e8ncies b\u00e0siques (Android Studio normalment ja les posa). Exemple rellevant:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">android {\n    compileSdk 34\n\n    defaultConfig {\n        applicationId \"com.example.tts_cat\"\n        minSdk 21\n        targetSdk 34\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    \/\/ no cal activar Compose\n}\n\ndependencies {\n    implementation \"androidx.core:core-ktx:1.10.1\"\n    implementation \"androidx.appcompat:appcompat:1.6.1\"\n    implementation \"com.google.android.material:material:1.9.0\"\n}\n<\/pre>\n\n\n\n<p>(Si Android Studio usa versions diferents, deixa les que proposa; el m\u00e9s important: <strong>no<\/strong> fer servir depend\u00e8ncies de Compose.)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3) Layout XML: <code>res\/layout\/activity_main.xml<\/code><\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?>\n&lt;androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\n    xmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\"\n    android:id=\"@+id\/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\"\n    android:padding=\"16dp\">\n\n    &lt;!-- Contenidor (equivalent \"div\") -->\n    &lt;LinearLayout\n        android:id=\"@+id\/container\"\n        android:orientation=\"vertical\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"12dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        android:background=\"?attr\/selectableItemBackground\">\n        \n        &lt;!-- EditText amb text precarregat en catal\u00e0 -->\n        &lt;EditText\n            android:id=\"@+id\/etText\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"Introdueix text...\"\n            android:inputType=\"textMultiLine\"\n            android:minLines=\"3\"\n            android:gravity=\"top|start\"\n            android:text=\"Hola! Aquest \u00e9s un missatge de prova en catal\u00e0.\" \/>\n\n        &lt;!-- Botons en fila -->\n        &lt;LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:layout_marginTop=\"12dp\">\n\n            &lt;Button\n                android:id=\"@+id\/btnSpeak\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:text=\"Parlar\" \/>\n\n            &lt;Button\n                android:id=\"@+id\/btnStop\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_weight=\"1\"\n                android:text=\"Parar\" \/>\n        &lt;\/LinearLayout>\n    &lt;\/LinearLayout>\n\n    &lt;!-- Opcional: text d'estat a la part de baix -->\n    &lt;TextView\n        android:id=\"@+id\/tvStatus\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Estat: inactiu\"\n        android:layout_marginTop=\"16dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id\/container\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\" \/>\n\n&lt;\/androidx.constraintlayout.widget.ConstraintLayout>\n<\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Nota: he posat un <code>LinearLayout<\/code> dins <code>ConstraintLayout<\/code> per crear el &#8220;contenidor&#8221; que demanaves (equivalent d\u2019un <code>&lt;div&gt;<\/code>). L\u2019<code>EditText<\/code> arriba amb text precarregat.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4) <code>MainActivity.kt<\/code> (Kotlin) \u2014 l\u00f2gica completa TTS<\/h2>\n\n\n\n<p>Copia aquest fitxer (modifica el package per adaptar-lo a <code>applicationId<\/code> si cal):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.example.tts_cat\n\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.speech.tts.TextToSpeech\nimport android.speech.tts.TextToSpeech.Engine\nimport android.widget.Button\nimport android.widget.EditText\nimport android.widget.TextView\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport java.util.Locale\n\nclass MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {\n\n    private var tts: TextToSpeech? = null\n\n    private lateinit var etText: EditText\n    private lateinit var btnSpeak: Button\n    private lateinit var btnStop: Button\n    private lateinit var tvStatus: TextView\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        \/\/ bind views\n        etText = findViewById(R.id.etText)\n        btnSpeak = findViewById(R.id.btnSpeak)\n        btnStop = findViewById(R.id.btnStop)\n        tvStatus = findViewById(R.id.tvStatus)\n\n        \/\/ Inicialitza TextToSpeech\n        tts = TextToSpeech(this, this)\n\n        btnSpeak.setOnClickListener {\n            val text = etText.text.toString().ifBlank { \"Hola! Aquest \u00e9s un missatge de prova en catal\u00e0.\" }\n            speakText(text)\n        }\n\n        btnStop.setOnClickListener {\n            stopSpeaking()\n        }\n    }\n\n    override fun onInit(status: Int) {\n        if (status == TextToSpeech.SUCCESS) {\n            \/\/ Intentem establir catal\u00e0 (Catalonia, Espanya)\n            val localeCa = Locale(\"ca\", \"ES\")\n            val result = tts?.setLanguage(localeCa)\n\n            if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {\n                \/\/ La veu catalana no est\u00e0 disponible\n                tvStatus.text = \"Estat: Catal\u00e0 no disponible, s'utilitza idioma per defecte.\"\n                Toast.makeText(this, \"Veu catalana no disponible. Pots instal\u00b7lar dades TTS.\", Toast.LENGTH_LONG).show()\n\n                \/\/ Prova d'obrir instal\u00b7lador de dades TTS (pot permetre descarregar veus)\n                try {\n                    val installIntent = Intent(Engine.ACTION_INSTALL_TTS_DATA)\n                    startActivity(installIntent)\n                } catch (e: Exception) {\n                    \/\/ Si no \u00e9s suportat, nom\u00e9s fem servir l'idioma per defecte\n                    tts?.language = Locale.getDefault()\n                }\n\n            } else {\n                \/\/ Tot OK: catal\u00e0 disponible\n                tvStatus.text = \"Estat: TTS inicialitzat (catal\u00e0).\"\n                \/\/ Opcional: parla autom\u00e0ticament en obrir l'app\n                speakText(\"Hola Android! Benvingut a l'aplicaci\u00f3.\")\n            }\n        } else {\n            tvStatus.text = \"Estat: Error al inicialitzar TTS.\"\n            Toast.makeText(this, \"No s'ha pogut inicialitzar TextToSpeech.\", Toast.LENGTH_SHORT).show()\n        }\n    }\n\n    private fun speakText(text: String) {\n        if (tts == null) return\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            \/\/ id d'utt\n            tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, \"UTTERANCE_ID\")\n        } else {\n            @Suppress(\"DEPRECATION\")\n            tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null)\n        }\n        tvStatus.text = \"Estat: parlant...\"\n    }\n\n    private fun stopSpeaking() {\n        tts?.stop()\n        tvStatus.text = \"Estat: aturat\"\n    }\n\n    override fun onDestroy() {\n        tts?.stop()\n        tts?.shutdown()\n        super.onDestroy()\n    }\n}\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Explicaci\u00f3 r\u00e0pida del codi<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>La <code>MainActivity<\/code> implementa <code>TextToSpeech.OnInitListener<\/code>.<\/li>\n\n\n\n<li><code>tts = TextToSpeech(this, this)<\/code> inicialitza el motor.<\/li>\n\n\n\n<li><code>onInit<\/code> comprova si el catal\u00e0 (<code>Locale(\"ca\",\"ES\")<\/code>) est\u00e0 suportat. Si no hi \u00e9s, intenta obrir l\u2019instal\u00b7lador de dades TTS (<code>Engine.ACTION_INSTALL_TTS_DATA<\/code>).<\/li>\n\n\n\n<li><code>speakText(...)<\/code> crida <code>tts.speak(...)<\/code>. Per API &gt;= 21 s&#8217;usa el par\u00e0metre <code>utteranceId<\/code>.<\/li>\n\n\n\n<li><code>stopSpeaking()<\/code> atura la parla.<\/li>\n\n\n\n<li>A <code>onDestroy<\/code> tanquem i alliberem recursos.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5) Strings (opcional)<\/h2>\n\n\n\n<p>Pots posar els textos a <code>res\/values\/strings.xml<\/code> si vols:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;resources>\n    &lt;string name=\"app_name\">tts_cat&lt;\/string>\n    &lt;string name=\"hello_default\">Hola! Aquest \u00e9s un missatge de prova en catal\u00e0.&lt;\/string>\n    &lt;string name=\"btn_speak\">Parlar&lt;\/string>\n    &lt;string name=\"btn_stop\">Parar&lt;\/string>\n&lt;\/resources>\n<\/pre>\n\n\n\n<p>I a l\u2019XML utilitzar <code>@string\/\u2026<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6) Provar l\u2019app<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Executa l\u2019aplicaci\u00f3 en un dispositiu f\u00edsic (recomanat) o un emulador amb suport TTS.<\/li>\n\n\n\n<li>Si no s\u2019escolta catal\u00e0, Android normalment et deixar\u00e0 descarregar paquets de veu a <strong>Configuraci\u00f3 \u2192 Accessibilitat \/ Entrada i Idioma \u2192 Sortida de text a veu<\/strong> (pot variar segons fabricant). Tamb\u00e9 el codi obrir\u00e0 el flux d\u2019instal\u00b7laci\u00f3 de dades TTS.<\/li>\n\n\n\n<li>El bot\u00f3 <strong>Parlar<\/strong> llegir\u00e0 el text del <code>EditText<\/code>. El bot\u00f3 <strong>Parar<\/strong> aturar\u00e0 la parla.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7) Millores opcionals (per si vols ampliar)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Afegir un <strong>spinner<\/strong> per triar idioma (ca, es, en) dins la mateixa Activity.<\/li>\n\n\n\n<li>Guardar l\u2019\u00faltim text escrit amb <code>SharedPreferences<\/code>.<\/li>\n\n\n\n<li>Afegir controls de velocitat (<code>setSpeechRate<\/code>) i to (<code>setPitch<\/code>) per l&#8217;usuari.<\/li>\n\n\n\n<li>Gestionar esdeveniments d\u2019<code>UtteranceProgressListener<\/code> per actualitzar UI quan acaba la parla.<\/li>\n<\/ul>\n\n\n\n<p>Exemple r\u00e0pid per ajustar velocitat:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">tts?.setSpeechRate(1.0f) \/\/ 1.0 \u00e9s normal, 0.5 m\u00e9s lent, 2.0 m\u00e9s r\u00e0pid\ntts?.setPitch(1.0f)      \/\/ 1.0 normal\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Si vols, ara:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Et faig el <strong>codi amb selector d\u2019idiomes<\/strong> dins la mateixa <code>MainActivity.kt<\/code>; o<\/li>\n\n\n\n<li>Et dono un fitxer <code>activity_main.xml<\/code> amb una mica m\u00e9s d\u2019estil; o<\/li>\n\n\n\n<li>T\u2019explico com instal\u00b7lar la veu catalana des del dispositiu pas a pas.<\/li>\n<\/ul>\n\n\n\n<p>Quina d\u2019aquestes prefereixes que fem ara? (si vols, faig directament el selector d\u2019idiomes dins la mateixa Activity)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Perfecte \u2014 et dono una guia pas a pas (en catal\u00e0) per crear una app Android (no Compose) anomenada tts_cat que fa servir TextToSpeech. La UI ser\u00e0 basada en XML i tindr\u00e0: Segueix aquests passos i copia\/enganxa els fitxers. 1) &hellip; <a href=\"https:\/\/www.beseit.net\/?page_id=17059\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3167,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"categories":[],"class_list":["post-17059","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17059","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=17059"}],"version-history":[{"count":3,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17059\/revisions"}],"predecessor-version":[{"id":17072,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17059\/revisions\/17072"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3167"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=17059"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=17059"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}