Construindo os Fragmentos de Conteúdo Local - YouTuber Android App - Parte 5

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Construindo os Fragmentos de Conteúdo Local - YouTuber Android App - Parte 5

Construindo os Fragmentos de Conteúdo Local - YouTuber Android App - Parte 5

Vinícius Thiengo
(470) (2)
Go-ahead
"É preciso encarar todo retrocesso, fracasso ou dificuldade como provações ao longo do caminho, como sementes plantadas para a colheita futura. Nenhum momento é desperdiçado se você presta atenção nas lições contidas em cada experiência."
Robert Greene
Kotlin Android
Capa do livro Mapas Android de Alta Qualidade - Masterização Android
TítuloMapas Android de Alta Qualidade - Masterização Android
CategoriasAndroid, Kotlin, Masterização, Especialização
AutorVinícius Thiengo
Edição
Ano2020
Capítulos11
Páginas166
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos continuar com o nosso projeto de aplicativo Android para YouTubers.

Nesta parte cinco nós vamos desenvolver todos os fragmentos que têm como fonte de dados somente conteúdos disponíveis em persistência local do aplicativo.

Animação do app Android passando pelas telas de conteúdo estático

São justamente os fragmentos desta parte do projeto que são totalmente opcionais em outras versões do app para outros canais do YouTube.

Bom, é isso. Vamos aos códigos...

... antes de prosseguir, saiba que:

A versão e-book (PDF 📙) do projeto completo está disponível somente para os inscritos da lista de e-mails 📧 do Blog.

Se você se inscrever ainda hoje é possível que o link do e-book esteja disponível na página de confirmação de sua inscrição na lista de e-mails do Blog.

A seguir os tópicos que estaremos abordando neste quinto conteúdo do projeto de aplicativo Android para YouTubers:

O que já temos até aqui

Se você chegou no projeto somente agora, saiba que este não é o primeiro e nem mesmo o último artigo já publicado sobre essa proposta de aplicativo Android.

Todo o roteiro de construção do projeto está na listagem a seguir:

Para tirar o máximo proveito do projeto de aplicativo que estaremos desenvolvendo... para isso é inteligente seguir cada um dos conteúdos na ordem apresentada na lista anterior.

Repositório

Para ter acesso a todos os códigos fontes do projeto já finalizado, entre no repositório GitHub dele em:

➙ GitHub do projeto de aplicativo Android para YouTuber.

Quais fragmentos

Como falei logo no início deste artigo: aqui teremos os fragmentos opcionais em projeto, porém importantes para o canal Vinícius Thiengo.

Pode ser que alguns dos contextos de fragmentos que você venha a estudar e a aplicar nesta parte de nosso projeto não sejam necessários em canais de YouTubers clientes que você venha a atender.

Porém mesmo assim recomendo que siga passo a passo todos os códigos e as dicas disponíveis aqui.

Pois assim será mais tranquilo, por exemplo, adicionar telas para outras necessidades exclusivas que os seus clientes tenham.

Enfim.

Aqui trabalharemos todos esses fragmentos opcionais em projeto e que os dados são exclusivamente locais, em "persistências fixas" (logo logo entrarei nos méritos de uma persistência fixa).

Sendo assim, estaremos construindo os fragmentos que representam as seguintes telas:

  • Tela de redes do canal;
  • Tela de grupos exclusivos do canal;
  • Tela de livros do canal;
  • Tela de cursos do canal;
  • Tela de contatos comerciais do canal;
  • Tela com o conteúdo mais completo sobre a proposta do canal (a única tela listada aqui que não tem como parte do layout um framework de lista).

So, let's code.

Fragmento de Redes

Está é a primeira das classes com lista de itens que herdam do fragmento comum que criamos em um ponto anterior do projeto, mais precisamente o fragmento FrameworkListFragment.

Essa tela apresentará as outras redes (sociais, sites, ...) vinculadas ao canal YouTube do app.

E também permitirá que o usuário acesse, com um simples toque em item de lista, qualquer uma dessas redes sociais, blogs, portais, ...

Tela de rede do canal YouTube do app Android

Estáticos de interface

Vamos iniciar com as novas configurações dos arquivos estáticos, XML, já presentes em projeto.

Digo "(...) já presentes em projeto", pois para todos os fragmentos que têm um framework de lista... para esses os arquivos estáticos já em projeto serão o suficiente.

Rótulos

No arquivo de Strings/res/values/strings.xml, adicione as seguintes novas definições:

...
<!-- SocialNetworksFragment -->
<string name="social_networks_content_title">
Todas as redes do canal com conteúdos exclusivos
</string>
<string name="social_network_toast_alert">
É preciso ao menos um aplicativo de navegador Web
para você poder acessar a rede \"%s\" do canal.
</string>
...

 

Note o uso de %s na String social_network_toast_alert.

Essa String é utilizada como template no método String.format() que vai gerar a String que será apresentada a partir do método callExternalApp() do fragmento ancestral FrameworkListFragment:

...
Toast
.makeText(
activity,
failMessage,
Toast.LENGTH_LONG
)
.show()
...

 

String.format() gerará o resultado referenciado em failMessage.

Está regra de negócio será verdadeira para todos os outros fragmentos que herdam de FrameworkListFragment.

Ícones vetoriais

Os ícones ajudam a identificar de maneira mais intuitiva as redes, tirando do usuário do app a obrigação de ter que ler todo o rótulo no item de lista.

Como em muitos outros pontos do projeto, aqui também estaremos utilizando ícones vetoriais.

Mais precisamente os ícones das seguintes redes / sites:

Ícone de Blog, ic_blog_color.xml:

Ícone vetorial do item Blog da área de Redes do app

Ícone do GitHubic_github_color.xml:

Ícone vetorial do item GitHub da área de Redes do app

Ícone do LinkedInic_linkedin_color.xml:

Ícone vetorial do item LinkedIn da área de Redes do app

Ícone da Udemy, ic_udemy_color.xml:

Ícone vetorial do item Udemy da área de Redes do app

Ícone do Facebookic_facebook_color.xml:

Ícone vetorial do item Facebook da área de Redes do app

Ícone do SlideShare, ic_slide_share_color.xml:

Ícone vetorial do item SlideShare da área de Redes do app

Ícone do Twitteric_twitter_color.xml:

Ícone vetorial do item Twitter da área de Redes do app

Ícone do YouTube (sim, eu escolhi colocar essa redundância para o app na versão do canal Thiengo), ic_youtube_color.xml:

Ícone vetorial do item YouTube da área de Redes do app

Se você estiver seguindo o projeto aqui passo a passo, então faça o download dos ícones anteriores e coloque-os no folder /res/drawable do projeto.

Saiba que na versão de projeto que está no GitHub...

.... nessa versão eu deixei ainda mais ícones vetoriais de redes sociais e blogs populares que podem ter vinculo com inúmeros canais no YouTube.

Basta dar uma olhada no folder /res/drawable do projeto no GitHub.

Classe de domínio

Enfim a classe que vai conter os dados de cada rede (ou site, ou portal, ...) vinculado ao canal do aplicativo.

No pacote /model crie a classe SocialNetwork com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.model

import android.net.Uri

/**
* Uma rede vinculada ao canal YouTube do
* aplicativo.
*
* O objetivo desta classe (objetos desta classe)
* é manter os dados que permitem acesso imediato a
* uma outra rede social (ou site, ou aplicativo,
* ...) que está vinculada ao canal YouTube do app.
*
* @property network nome da rede (aplicativo, site,
* ...) vinculada ao canal.
* @property accountName nome da conta na rede
* vinculada ao canal.
* @property appUri [Intent] URI da conta na rede
* vinculada ao canal.
* @property webUri URL da conta na rede vinculada
* ao canal.
* @property logo ícone do aplicativo da rede
* vinculada ao canal.
* @constructor cria um objeto completo do tipo
* [SocialNetwork].
*/
class SocialNetwork(
val network: String,
private val accountName: String,
private val appUri: String,
private val webUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= String.format(
"%s: %s",
network,
accountName
)

override fun getWebUri()
= Uri.parse( webUri )

override fun getAppUri()
= Uri.parse( appUri )

override fun getIcon()
= logo
}

 

Classe simples, implementando somente o necessário.

E como de costume:

Não deixe de ler todos os comentários presentes nos códigos (dinâmicos e estáticos).

Pois eles ajudam consideravelmente no entendimento de todo o projeto de aplicativo.

Persistência estática de dados

A "classe persistência" de redes do canal também vai ficar em /data/fixed. Exatamente como fizemos com a classe persistência de itens de menu principal (nosso bottom menu).

Sendo assim, no pacote /data/fixed, crie a classe SocialNetworksData como a seguir:

package thiengo.com.br.canalvinciusthiengo.data.fixed

import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.SocialNetwork

/**
* Contém os dados das outras redes (sociais ou sites),
* vinculados ao canal YouTube do aplicativo.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados dessas outras redes.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novas redes)
* e com espaços de tempo longos entre as alterações,
* então a melhor escolha foi o trabalho deles em
* uma classe estática (companion object) que
* trabalha como se fosse uma persistência de dados
* estáticos.
*/
class SocialNetworksData {

companion object{
/**
* Retorna todas as redes vinculadas ao
* canal.
*
* @return lista não mutável de objetos
* [SocialNetwork].
*/
fun getNetworks()
= listOf(
SocialNetwork(
network = "Blog",
accountName = "Thiengo.com.br",
appUri = "",
webUri = "https://www.thiengo.com.br",
logo = R.drawable.ic_blog_color
),
SocialNetwork(
network = "Github",
accountName = "/viniciusthiengo",
appUri = "",
webUri = "https://github.com/viniciusthiengo",
logo = R.drawable.ic_github_color
),
SocialNetwork(
network = "LinkedIn",
accountName = "/vinícius-thiengo",
appUri = "linkedin://profile/vinícius-thiengo-5179b180",
webUri = "https://www.linkedin.com/in/vin%C3%ADcius-thiengo-5179b180",
logo = R.drawable.ic_linkedin_color
),
SocialNetwork(
network = "Udemy",
accountName = "/vinícius-thiengo",
appUri = "",
webUri = "https://www.udemy.com/user/vinicius-thiengo/?persist_locale&locale=pt_BR",
logo = R.drawable.ic_udemy_color
),
SocialNetwork(
network = "Facebook",
accountName = "/thiengoCalopsita",
appUri = "",
webUri = "https://www.facebook.com/thiengoCalopsita",
logo = R.drawable.ic_facebook_color
),
SocialNetwork(
network = "SlideShare",
accountName = "/VinciusThiengo",
appUri = "",
webUri = "https://www.slideshare.net/VinciusThiengo",
logo = R.drawable.ic_slide_share_color
),
SocialNetwork(
network = "Twitter",
accountName = "/thiengoCalops",
appUri = "",
webUri = "https://twitter.com/thiengoCalops",
logo = R.drawable.ic_twitter_color
),
SocialNetwork(
network = "YouTube",
accountName = "/ThiengoCalopsita",
appUri = "",
webUri = "https://www.youtube.com/user/thiengoCalopsita",
logo = R.drawable.ic_youtube_color
)
)
}
}

 

Thiengo, por que você não colocou ao menos os rótulos de cada propriedade network no arquivo /res/values/strings.xml? Exatamente como você fez com outras Strings estáticas do projeto até aqui.

Excelente pergunta.

Devemos lembrar que o arquivo strings.xml é principalmente para Strings estáticas do projeto e que são passíveis de tradução, internacionalização.

Ou seja, se o projeto de aplicativo passar a atender a mais de um idioma, teremos então mais de um arquivo strings.xml.

Confesso que, apesar de ser pouco comum, é possível utilizar o arquivo strings.xml para Strings estáticas que são comuns em mais de um ponto do projeto de aplicativo (se repetem)... mesmo que elas não sejam passíveis de tradução.

Em resumo:

Os rótulos em cada propriedade network do código anterior são rótulos não passíveis de tradução. São os nomes (próprios) das redes nas quais o canal é vinculado.

Esse é o comportamento comum em projetos Android: o que não é passível de tradução, não entra em strings.xml.

É isso.

Vamos ao trecho final desta Tela de Redes.

SocialNetworksFragment

Por fim o fragmento, que devido às definições em FrameworkListFragment será bem pequeno quando comparado à classe ancestral.

No pacote /ui/fragment crie o fragmento SocialNetworksFragment exatamente com os fontes a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.data.fixed.SocialNetworksData
import thiengo.com.br.canalvinciusthiengo.model.SocialNetwork
import thiengo.com.br.canalvinciusthiengo.ui.adapter.ListItemAdapter

/**
* Contém a lista de redes do canal YouTube do
* app.
*
* @constructor cria um objeto completo do tipo
* [SocialNetworksFragment].
*/
class SocialNetworksFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [SocialNetworksFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "SocialNetworksFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.social_networks_content_title )
)

val adapter = ListItemAdapter(
context = activity!!,
items = SocialNetworksData.getNetworks(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
appUri = item.getAppUri(),
failMessage = String.format(
getString( R.string.social_network_toast_alert ),
(item as SocialNetwork).network
)
)
}
)

initList( adapter = adapter )
}
}

 

Não se preocupe agora com o "porquê" da constante KEY.

Quando chegar o momento de configurar os fragmentos junto às opções de menu principal do app... nesse momento ficará fácil entender o porquê desta constante.

Os outros fragmentos, todos eles (incluindo os que não herdam de FrameworkListFragment), também terão as suas próprias constantes KEY.

Mas confesso que quando eu desenvolvi o projeto, essa parte de constante devido a problemas de retenção em memória... essa parte em específico eu resolvi somente depois que o projeto já estava na fase de testes.

Porém eu já adiantei essa configuração aqui, pois, acredite, não vai atrapalhar em nada o estudo e o entendimento completo deste projeto de aplicativo Android.

Assim podemos partir para a próxima tela.

Fragmento de Grupos

Agora vamos à construção de todos os recursos do fragmento de "grupos exclusivos" do canal. Grupos como os de Facebook, WhatsApp, Telegram e Discord.

Tela de grupos exclusivos do canal YouTube do app Android

Note que é possível colocar qualquer tipo de grupo, até mesmo os que têm endereço físico se você utilizar Mapas de Alta Qualidade via intenções.

Aqui nós vamos utilizar grupos reais e públicos, mas não grupos exclusivos do canal YouTube Thiengo. Pois o canal do Blog não tem (pode acreditar) um grupo específico somente de seguidores.

Estáticos de interface

Já como um padrão em todos os artigos da série: vamos iniciar com a parte fácil, estática, e então prosseguir para os códigos dinâmicos.

Rótulos

No arquivo XML /res/values/strings.xml adicione as novas Strings a seguir:

...
<!-- GroupsFragment -->
<string name="groups_content_title">
Grupos exclusivos do canal
</string>
<string name="groups_desc">A
seguir todos os grupos exclusivos do canal que você
pode fazer parte. Estamos esperando por você também
nas nossas comunidades exclusivas.
</string>
<string name="groups_toast_alert">
É preciso o aplicativo do \"%s\" para abrir o grupo
exclusivo \"%s\".
</string>
...

Ícones vetoriais

Agora os ícones de identificação da rede (aplicativo, site, ...) do grupo.

Todos os ícones utilizados aqui são vetoriais e mesmo se você estiver planejando modificações grandes no projeto...

... mesmo com esse pensamento eu recomendo que você mantenha sempre, para ícones de sistema, o uso de drawables vetoriais.

Desta forma ao menos a manutenção do projeto será facilitada consideravelmente.

Os ícones para redes de grupos que vamos utilizar são os seguintes:

Ícone de Grupo no Facebook, ic_facebook_group_color.xml:

Ícone vetorial do item Grupo Facebook da área de Grupos do app

Ícone de Grupo no WhatsApp, ic_whatsapp_color.xml:

Ícone vetorial do item Grupo WhatsApp da área de Grupos do app

Ícone de Grupo no Telegram, ic_telegram_color.xml:

Ícone vetorial do item Grupo Telegram da área de Grupos do app

Ícone de Grupo no Discord, ic_discord_color.xml:

Ícone vetorial do item Grupo Discord da área de Grupos do app

Ícone de Grupo no LinkedIn, ic_linkedin_group_color.xml:

Ícone vetorial do item Grupo LinkedIn da área de Grupos do app

Faça o download dos ícones e coloque-os no folder /res/drawable do projeto.

É possível que alguns dos canais que você atenda não tenham grupos vinculados a eles. Desta forma é somente remover toda a parte de grupos do projeto.

Alias este é o comportamento que você pode ter com qualquer tela, deste projeto de aplicativo, que não seja útil ao canal YouTube que está sendo configurado em app.

Classe de domínio

A classe de domínio é bem simples, principalmente porque ela também implementa ListItem.

Em /model adicione a classe Group como a seguir:

package thiengo.com.br.canalvinciusthiengo.model

import android.net.Uri

/**
* Um grupo que é administrado ou apenas indicado
* pelo proprietário do canal YouTube vinculado
* ao aplicativo.
*
* O objetivo desta classe (objetos desta classe)
* é manter os principais dados do grupo para que
* o seguidor do canal consiga avalia-lo antes
* mesmo de acessa-lo e assim decidir se vai ou
* não fazer parte dele.
*
* @property place local digital ou não do grupo.
* @property name nome do grupo.
* @property webUri URL do grupo no app / site de
* acesso a ele.
* @property logo ícone de identificação do
* app / local do grupo.
* @constructor cria um objeto completo do tipo
* [Group].
*/
class Group(
val place: String,
val name: String,
private val webUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= String.format(
"%s: %s",
place,
name
)

override fun getWebUri()
= Uri.parse( webUri )

override fun getIcon()
= logo
}

 

Bem simples, certo?

Persistência estática de dados

Assim a nossa classe persistência.

No folder /data/fixed adicione a classe abstrata GroupsData exatamente como a seguir:

package thiengo.com.br.canalvinciusthiengo.data.fixed

import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.Group

/**
* Contém os dados dos grupos, de outras redes,
* vinculados ao canal YouTube do aplicativo.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados dos grupos.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos grupos)
* e com espaços de tempo longos entre as alterações,
* então a melhor escolha foi o trabalho deles em
* uma classe estática (companion object) que
* trabalha como se fosse uma persistência de dados
* estáticos.
*/
abstract class GroupsData {

companion object{
/**
* Retorna todos os grupos vinculados ao
* canal.
*
* @return lista não mutável de objetos
* [Group].
*/
fun getGroups()
= listOf(
Group(
place = "Facebook",
name = "Desenvolvimento Mobile",
webUri = "https://www.facebook.com/groups/246149505467359",
logo = R.drawable.ic_facebook_group_color
),
Group(
place = "WhatsApp",
name = "\uD83D\uDCAF% Android",
webUri = "https://chat.whatsapp.com/HLXgiKgC6o96q8UWGjiidW",
logo = R.drawable.ic_whatsapp_color
),
Group(
place = "Telegram",
name = "DevCodeBr - Android/Java/Kotlin",
webUri = "https://t.me/devcodebr_android",
logo = R.drawable.ic_telegram_color
),
Group(
place = "Discord",
name = "Android Discord",
webUri = "https://discord.gg/B8XEDGC",
logo = R.drawable.ic_discord_color
),
Group(
place = "LinkedIn",
name = "Android Developers Brazil",
webUri = "https://www.linkedin.com/groups/4447810/",
logo = R.drawable.ic_linkedin_group_color
)
)
}
}

 

Note que o valor \uD83D\uDCAF em:

...
Group(
...,
name = "\uD83D\uDCAF% Android",
...
)
...

 

Este valor é um unicode (💯) que será corretamente interpretado pelo sistema Android que estiver executando o nosso aplicativo.

Interpretado em tela como a seguir:

Item de lista com código unicode sendo interpretado pelo sistema Android

Emojis também são aceitos em Strings estáticas ou dinâmicas Android. Logo logo chegaremos a um trecho que faz uso de um emoji de câmera.

Com isso podemos ir à parte final da tela de Grupos exclusivos do canal.

GroupsFragment

Como no fragmento de redes do canal, aqui o fragmento também será bem simples devido à herança de FrameworkListFragment.

Sendo assim, no pacote /ui/fragment, adicione a classe GroupsFragment com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.data.fixed.GroupsData
import thiengo.com.br.canalvinciusthiengo.model.Group
import thiengo.com.br.canalvinciusthiengo.ui.adapter.ListItemAdapter

/**
* Contém a lista de grupos do canal YouTube do
* app.
*
* @constructor cria um objeto completo do tipo
* [GroupsFragment].
*/
class GroupsFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [GroupsFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "GroupsFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.groups_content_title ),
subTitleText = getString( R.string.groups_desc )
)

val adapter = ListItemAdapter(
context = activity!!,
items = GroupsData.getGroups(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
failMessage = String.format(
getString( R.string.groups_toast_alert ),
(item as Group).place,
item.name
)
)
}
)

initList( adapter = adapter )
}
}

 

Note que em failMessage no argumento nomeado callExternalAppCallback foi preciso aplicar um casting:

...
failMessage = String.format(
...,
(item as Group).place,
...
)
...

 

Isso, pois foi necessário obter o valor correto de String do objeto item, que é definido neste trecho de código como um tipo ListItem.

Porém neste mesmo trecho de código nós sabemos que o objeto na verdade é um objeto do tipo Group que também implementa ListItem.

Ou seja, é completamente aceitável e seguro realizar esse casting nesta parte do código.

Done.

Mais um fragmento finalizado.

Ainda teremos alguns que vão seguir a mesma linha de código.

O importante é entender a estrutura comum, pois é provável que para alguns canais do YouTube você tenha telas de contextos diferentes, mas também com listas de itens.

Desta forma basta replicar a estrutura e mudar:

  • Rótulos;
  • Ícones;
  • Classe de domínio;
  • Classe persistência;
  • e fragmento filho de FrameworkListFragment.

É isso. Vamos ao próximo fragmento.

Fragmento de Livros

Este certamente é um tipo de fragmento, tela, que a maioria dos canais do YouTube que você venha a atender não terá.

Como eu também sou autor de livros, vi como prudente colocar essa screen em aplicativo:

Tela dos livros do canal YouTube do app Android

É isso, vamos aos códigos.

Estáticos de interface

Vamos iniciar com a atualização da parte estática de projeto.

Rótulos

Para as Strings, no arquivo /res/values/strings.xml, adicione as seguintes novas definições:

...
<!-- BooksFragment -->
<string name="books_content_title">
Conheça meus livros
</string>
<string name="books_toast_alert">
É preciso ao menos um navegador Web para abrir a
página do livro \"%s\".
</string>
...

Ícones vetoriais

Agora os ícones vetoriais para ajudar na identificação única de cada livro.

Vou ser sincero...

... utilizei o mesmo ícone base para todos os livros, porém no XML de cada um deles eu coloquei como cor de capa a cor de destaque de cada livro (viva ao drawable vetorial que permite esse tipo de alteração de maneira bem simples).

Os ícones de livros (cinco até o momento da construção deste artigo) são os seguintes:

Ícone do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a diaic_book_cover_red.xml:

Ícone vetorial do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia

Ícone do livro Refatorando Para Programas Limposic_book_cover_yellow.xml:

Ícone vetorial do livro Refatorando Para Programas Limpos

Ícone do livro Receitas Para Desenvolvedores Androidic_book_cover_grey.xml:

Ícone vetorial do livro Receitas Para Desenvolvedores Android

Ícone dos livros "Porque e Como Utilizar Vetores no Android" e "Construa Um Aplicativo Android Completo Para YouTubers" (ambos com a cor de destaque sendo laranja), ic_book_cover_orange.xml:

Ícone vetorial dos livros gratuitos aos inscritos do Blog

Faça o download dos ícones e coloque-os no folder /res/drawable de sua versão de projeto.

Classe de domínio

Uma outra entidade simples de domínio de problema.

No pacote /model adicione a classe Book com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.model

import android.net.Uri

/**
* Um livro autoral ou apenas indicado pelo
* proprietário do canal YouTube vinculado ao
* aplicativo.
*
* O objetivo desta classe (objetos desta classe)
* é apenas manter os dados importantes para
* apresentação do livro vinculado ao canal
* YouTube do aplicativo.
*
* @property title título do livro.
* @property categories categorias do livro.
* @property webPage URL da página Web do livro.
* @property cover identificador único do ícone
* que simula a capa do livro.
* @constructor cria um objeto completo do tipo
* [Book].
*/
class Book(
val title: String,
private val categories: List<String>,
private val webPage: String,
private val cover: Int ) : ListItem {

override fun getMainText()
= title

override fun getFirstAuxText()
= categories.joinToString(", ")

override fun getWebUri()
= Uri.parse( webPage )

override fun getIcon()
= cover
}

Persistência estática de dados

Agora a classe de persistência estática de todos os livros que serão listados.

Antes de apresentar a classe, saiba que todas as telas que dependem de persistência estática, caso seja necessária a adição ou remoção de informações...

... essas classes em atualização exigem que uma nova versão do aplicativo seja gerada e então enviada à Google Play Store.

Isso não é um tática para "bater carteira" do YouTuber cliente.

É simplesmente uma constatação que o app é simples e o foco real dele é notificar o seguidor que tem um novo vídeo liberado.

Voltando à classe persistência de livros...

... no pacote /data/fixed crie a classe abstrata BooksData com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.data.fixed

import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.Book

/**
* Contém os dados dos livros proprietários ou
* apenas indicados pelo canal YouTube vinculado
* ao aplicativo.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados dos livros.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos livros)
* e com espaços de tempo longos entre as
* alterações, então a melhor escolha foi o trabalho
* deles em uma classe estática (companion object)
* que trabalha como se fosse uma persistência de
* dados estáticos.
*/
abstract class BooksData {

companion object {
/**
* Retorna todos os livros (digitais ou não)
* vinculados ao canal.
*
* @return lista não mutável de objetos
* [Book].
*/
fun getBooks()
= listOf(
Book(
title = "Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia",
categories = listOf("Android", "Kotlin"),
webPage = "https://www.thiengo.com.br/livro-desenvolvedor-kotlin-android",
cover = R.drawable.ic_book_cover_red
),
Book(
title = "Refatorando Para Programas Limpos",
categories = listOf("Engenharia de Software", "Código limpo"),
webPage = "https://www.thiengo.com.br/livro-refatorando-para-programas-limpos",
cover = R.drawable.ic_book_cover_yellow
),
Book(
title = "Receitas Para Desenvolvedores",
categories = listOf("Android", "Java"),
webPage = "https://www.thiengo.com.br/livro-receitas-para-desenvolvedores-android",
cover = R.drawable.ic_book_cover_grey
),
Book(
title = "Porque e Como Utilizar Vetores no Android",
categories = listOf("Android", "Kotlin", "Drawable"),
webPage = "https://www.thiengo.com.br/construa-um-aplicativo-android-completo-para-youtubers-parte-1",
cover = R.drawable.ic_book_cover_orange
),
Book(
title = "Construa Um Aplicativo Android Completo Para YouTubers",
categories = listOf("Android", "Kotlin", "YouTuber"),
webPage = "https://www.thiengo.com.br/construa-um-aplicativo-android-completo-para-youtubers-parte-1",
cover = R.drawable.ic_book_cover_orange
)
)
}
}

 

Você vai notar que os links dos livros "Porque e Como Utilizar Vetores no Android" e "Construa Um Aplicativo Android Completo Para YouTubers" são na verdade links dos respectivos artigos em Blog.

Isso, pois estes livros estão liberados somente aos inscritos da lista de e-mails do Blog.

BooksFragment

E por fim o fragmento de livros que também herda de FrameworkListFragment devido ao trabalho com um framework de lista e também com uma persistência estática.

Em /ui/fragment adicione a classe BooksFragment com o exato código fonte a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.data.fixed.BooksData
import thiengo.com.br.canalvinciusthiengo.model.Book
import thiengo.com.br.canalvinciusthiengo.ui.adapter.ListItemAdapter

/**
* Contém a lista de livros vinculados ou apenas
* indicados pelo canal YouTube do app.
*
* @constructor cria um objeto completo do tipo
* [BooksFragment].
*/
class BooksFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [BooksFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "BooksFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.books_content_title )
)

val adapter = ListItemAdapter(
context = activity!!,
items = BooksData.getBooks(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
failMessage = String.format(
getString( R.string.books_toast_alert ),
(item as Book).title
)
)
}
)

initList( adapter = adapter )
}
}

 

É isso.

Vamos ao próximo fragmento, o de cursos.

Fragmento de Cursos

A tela deste fragmento tem uma necessidade muito similar à tela de livros: são poucos os canais que vão ter essa seção em aplicativo.

Tela dos cursos do canal YouTube do app Android

Este também é o fragmento que o item de lista utiliza todos os componentes visuais definidos no layout list_item.xml.

É isso, vamos aos códigos.

Estáticos de interface

Vamos iniciar com os códigos estáticos XML. Que dessa vez tem novidade: ícone (emoji) em conteúdo String.

Rótulos

No arquivo /res/values/strings adicione as seguintes novas Strings:

...
<!-- CoursesFragment -->
<string name="courses_content_title">
Conheça meus cursos em vídeo
</string>
<string name="complement_amount_videos">
🎥 %d vídeo aulas
</string>
<string name="course_toast_alert">
É preciso um navegador Web para abrir a página do
curso \"%s\".
</string>
...

 

É isso mesmo que você está vendo.

As Strings estáticas (e dinâmicas) no Android (ao menos a partir da API 21, Lollipop) podem sim se beneficiar dos emojis (imagens) em texto - 🎥.

E o template de String em complement_amount_videos funcionará like a charm:

Item de lista curso com emoji de câmera no texto

No site Facebook Symbols você tem alguns emojis gratuitos (e muito bem feitos) disponíveis.

Se você conhece mais alguns sites excelentes de emojis, não deixe de compartilhar nos comentários.

Ícones vetoriais

Aqui teremos um único ícone. Utilizei está estratégia também no fragmento de PlayLists do canal.

Isso, pois confesso que não vi necessidade (e nem facilidade) em encontrar, por exemplo, um ícone diferente para cada PlayList.

No caso do curso, até o momento da construção deste artigo eu tenho apenas um curso em vídeo disponível:

Ícone do curso Android: Prototipagem Profissional de Aplicativosic_courses_color.xml:

Ícone do curso Android: Prototipagem Profissional de Aplicativos

Faça o download do ícone anterior e coloque-o no folder /res/drawable do projeto de aplicativo.

Classe de domínio

Agora, no pacote /model, adicione a classe Course com o seguinte código fonte:

import android.content.res.Resources
import android.net.Uri
import thiengo.com.br.canalvinciusthiengo.R

/**
* Um curso ou apenas uma indicação de curso pelo
* proprietário do canal que está vinculado ao
* app.
*
* O objetivo desta classe (objetos desta classe)
* é apenas manter os dados importantes para
* apresentação do curso vinculado ao canal
* YouTube do aplicativo.
*
* @property title título do curso.
* @property categories categorias do curso.
* @property amountVideos quantidade de vídeo
* aulas presentes no curso.
* @property webPage URL da página Web do curso.
* @property cover identificador único do ícone
* vetorial do curso.
* @constructor cria um objeto completo do tipo
* [Course].
*/
class Course(
val title: String,
private val categories: List<String>,
private val amountVideos: Int,
private val webPage: String,
private val cover: Int ) : ListItem {

override fun getMainText()
= title

override fun getFirstAuxText()
= categories.joinToString( ", " )

override fun getSecondAuxText( resource: Resources )
= String.format(
resource.getString( R.string.complement_amount_videos ),
amountVideos
)

override fun getWebUri()
= Uri.parse( webPage )

override fun getIcon()
= cover
}

Persistência estática de dados

No pacote /data/fixed crie a classe persistência CoursesData com o seguinte código:

package thiengo.com.br.canalvinciusthiengo.data.fixed

import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.Course

/**
* Contém os dados dos cursos proprietários ou
* apenas indicados pelo canal YouTube vinculado
* ao aplicativo.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados dos cursos.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos cursos)
* e com espaços de tempo longos entre as
* alterações, então a melhor escolha foi o trabalho
* deles em uma classe estática (companion object)
* que trabalha como se fosse uma persistência de
* dados estáticos.
*/
abstract class CoursesData {

companion object{
/**
* Retorna todos os cursos (digitais ou não)
* vinculados ao canal.
*
* @return lista não mutável de objetos
* [Course].
*/
fun getCourses()
= listOf(
Course(
title = "Android: Prototipagem Profissional de Aplicativos",
categories = listOf("Android", "Prototipagem", "Lado estratégico"),
amountVideos = 186,
webPage = "https://www.udemy.com/android-prototipagem-profissional-de-aplicativos/?persist_locale&locale=pt_BR",
cover = R.drawable.ic_courses_color
)
)
}
}

 

Apesar de neste projeto de aplicativo estarmos trabalhando somente com curso digital na tela de cursos.

Apesar disso é perfeitamente possível colocar a URL (webPage) de Mapas de Alta Qualidade para cursos presenciais.

Assim, com a API turn-by-turn do Google Maps, o usuário é literalmente guiado até o local do curso.

CoursesFragment

Por fim, para a tela de cursos do canal, a classe fragmento dela.

No pacote /ui/fragment crie o fragmento CoursesFragment com o exato código a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.data.fixed.CoursesData
import thiengo.com.br.canalvinciusthiengo.model.Course
import thiengo.com.br.canalvinciusthiengo.ui.adapter.ListItemAdapter

/**
* Contém a lista de cursos vinculados ou apenas
* indicados pelo canal YouTube do app.
*
* @constructor cria um objeto completo do tipo
* [CoursesFragment].
*/
class CoursesFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [CoursesFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "CoursesFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.courses_content_title )
)

val adapter = ListItemAdapter(
context = activity!!,
items = CoursesData.getCourses(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
appUri = item.getAppUri(),
failMessage = String.format(
getString( R.string.course_toast_alert ),
(item as Course).title
)
)
}
)

initList( adapter = adapter )
}
}

 

Done!

Vamos ao próximo, e último, fragmento que herda de FrameworkListFragment.

Agora o fragmento de contatos comerciais.

Fragmento de Contatos Comerciais

Enfim o fragmento que é uma tela extra que certamente todos os canais de YouTube que resolverem fechar com você vão querer ter.

É aqui que o dinheiro "fala" com o YouTuber: patrocínios, anúncios, colabs, ...

Tela com os contatos comerciais do canal YouTube do app Android

E aqui, apesar de existir mais um fragmento com listagem de itens, este é último (para o canal Thiengo) que herdará de FrameworkListFragment.

So, let's code.

Estáticos de interface

Seguindo a "metodologia": primeiro o fácil.

Vamos iniciar com a atualização dos arquivos estáticos XML.

Rótulos

No arquivo de Strings, /res/values/strings, adicione as seguintes novas definições:

...
<!-- BusinessContactFragment -->
<string name="business_contacts_content_title">
Quer anunciar? Fechar negócios?
</string>
<string name="business_contacts_desc">A
seguir os canais de contato comercial com o
canal e o Blog.

\n\nIsso para que você possa colocar a sua marca em
todas as mídias do canal e assim crescer
exponencialmente a sua <em>brand</em>.
</string>
<string name="business_contact_toast_alert">
É preciso o aplicativo \"%s\" para poder entrar em
contato pelo \"%s\".
</string>
...

 

Note a falta de espaçamento em dois pontos da String business_contacts_desc.

Mais precisamente em:

...
<string name="business_contacts_desc">A
...

 

E em:

...
\n\nIsso para que você possa colocar a sua marca em
...

 

Isso é necessário quando há algum dado de formatação em texto (caractere especial como \n ou tags HTML, por exemplo).

Pois caso contrário o espaço é também interpretado em UI e o texto fica com um "espaço em branco perdido".

Por exemplo, ficaria "errado" como a seguir se essa estratégia (ou alguma outra também funcional) não fosse utilizada:

Texto do app Android sem a estratégia correta para remover espaço em branco extra

Ícones vetoriais

Para o canal Thiengo, serão apenas três ícones:

Ícone de contato pelo Blog, ic_blog_color.xml: (este você já baixou na lista de ícones vetoriais das Redes do canal)

Ícone vetorial de contato pelo Blog

Ícone de contato pelo Gmail, ic_gmail_color.xml:

Ícone vetorial de contato pelo Gmail

Ícone de contato pelo Facebook Messenger, ic_facebook_messenger_color.xml:

Ícone vetorial de contato pelo Facebook Messenger

Faça o download dos ícones e coloque-os no folder /res/drawable do projeto de aplicativo.

Classe de domínio

Agora o primeiro código dinâmico dessa tela, fragmento.

Para a classe de contato comercial, no pacote /model, adicione a entidade BusinessContact com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.model

import android.net.Uri

/**
* Um contato de negócio com o canal YouTube
* vinculado ao aplicativo. Por aqui é possível
* fechar acordos comerciais com o canal.
*
* O objetivo desta classe (objetos desta classe)
* é manter todos os dados importantes para o
* imediato contato entre (possível) cliente e
* proprietário do canal YouTube.
*
* @property place nome do aplicativo Android
* para contato.
* @property contact endereço de contato.
* @property webUri URL do endereço de
* contato no app / local de acesso a ele.
* @property logo ícone que identifica o
* app / local Android para contato.
* @constructor cria um objeto completo do tipo
* [BusinessContact].
*/
class BusinessContact(
val place: String,
val contact: String,
private val webUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= String.format(
"%s: %s",
place,
contact
)

override fun getWebUri()
= Uri.parse( webUri )

override fun getIcon()
= logo
}

Persistência estática de dados

No pacote /data/fixed adicione a classe persistência BusinessContactData como a seguir:

package thiengo.com.br.canalvinciusthiengo.data.fixed

import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.BusinessContact

/**
* Contém os dados de contatos comerciais
* vinculados ao canal YouTube do aplicativo.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* todos os dados de contatos comerciais.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos contatos)
* e com espaços de tempo longos entre as alterações,
* então a melhor escolha foi o trabalho deles em
* uma classe estática (companion object) que
* trabalha como se fosse uma persistência de dados
* estáticos.
*/
abstract class BusinessContactData {

companion object{
/**
* Retorna todos os contatos comerciais do
* canal.
*
* @return lista não mutável de objetos
* [BusinessContact].
*/
fun getBusinessContacts()
= listOf(
BusinessContact(
place = "Gmail",
contact = "thiengocalopsita@gmail.com",
webUri = "mailto:thiengocalopsita@gmail.com?Subject=Contato comercial - Thiengo",
logo = R.drawable.ic_gmail_color
),
BusinessContact(
place = "Blog",
contact = "Thiengo.com.br",
webUri = "https://www.thiengo.com.br/contato",
logo = R.drawable.ic_blog_color
),
BusinessContact(
place = "Facebook Messenger",
contact = "Vinícius Thiengo - Blog",
webUri = "http://m.me/thiengoCalopsita",
logo = R.drawable.ic_facebook_messenger_color
)
)
}
}

 

Note a webUri para que seja possível acionar algum serviço de e-mail do aplicativo:

...
webUri = "mailto:thiengocalopsita@gmail.com?Subject=Contato comercial - Thiengo",
...

 

Devido ao nosso código de configuração de Intent estar encapsulado no fragmento FrameworkListFragment, é preciso sempre utilizar URIs (Web ou não) passíveis de serem colocadas em intenções Android para ser possível abrir o aplicativo (ou site) correto.

Ou seja, se no projeto de canal que você estiver configurando houver o endereço do app "XYZ", então o correto é buscar pela URI dele para compartilhamento / acesso a contato.

Até mesmo para acionamento do aplicativo nativo de ligação telefônica tem URI:

...
/**
* tel:+[código internacional][código de área - se houver][número telefone]
*/
webUri = "tel:+5521999999999"
...

 

Aqui eu não coloquei, pois o canal Vinícius Thiengo não tem contato direto via ligação telefônica.

BusinessContactsFragment

Agora o fragmento de contatos comerciais.

Em /ui/fragment crie o fragmento BusinessContactsFragment com o seguinte código fonte:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.data.fixed.BusinessContactData
import thiengo.com.br.canalvinciusthiengo.model.BusinessContact
import thiengo.com.br.canalvinciusthiengo.ui.adapter.ListItemAdapter

/**
* Contém a lista de contatos comerciais do canal
* YouTube do app.
*
* @constructor cria um objeto completo do tipo
* [BusinessContactsFragment].
*/
class BusinessContactsFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [BusinessContactsFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "BusinessContactsFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.business_contacts_content_title ),
subTitleText = getString( R.string.business_contacts_desc )
)

val adapter = ListItemAdapter(
context = activity!!,
items = BusinessContactData.getBusinessContacts(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
appUri = item.getAppUri(),
failMessage = String.format(
getString( R.string.business_contact_toast_alert ),
(item as BusinessContact).place,
item.contact
)
)
}
)

initList( adapter = adapter )
}
}

 

É isso.

Vamos agora ao último fragmento que contém o conteúdo somente estático e local no aplicativo.

Fragmento Sobre o canal

Aqui não temos framework de lista e sim somente Strings estáticas.

Tela Sobre o canal YouTube do app Android

Já lhe adianto que mesmo não herdando de FrameworkListFragment esse será o fragmento com menor código dinâmico (código Kotlin) em projeto.

Além de ser o fragmento de uma tela que muito provavelmente você terá que ter em todas as versões de projeto que for desenvolver para os canais, YouTubers, que conseguir como clientes.

Vamos aos códigos.

Estáticos de interface

Vamos seguir com os arquivos estáticos XML primeiro.

Rótulos

Em /res/values/strings.xml adicione "todo o conteúdo" do fragmento Sobre como a seguir:

...
<!-- AboutChannelFragment -->
<string name="about_channel_content_title">
Conheça um pouco mais do porquê do canal
</string>
<string name="about_channel_full_desc">A
proposta do canal é mostrar, passo a passo, as mais
diversas APIs, bibliotecas e frameworks Android para
o desenvolvimento de aplicativos mobile com qualidade.

\n\nO canal é uma extensão do Blog Thiengo.com.br.

\n\nUma extensão em vídeo dos artigos que são disponibilizados
em texto e também (alguns deles) em PDF.

\n\nComo conteúdo também teremos a construção de alguns
aplicativos Android por completo.

\n\nAlguns desses conteúdos de apps completos serão em
formato de série. Outros em formato único, tudo em
uma única “tacada” (uma vídeo aula).

\n\nPara sugerir novos conteúdos e também tirar dúvidas
você pode estar entrando em contato com o Thiengo
pelos endereços a seguir:

\n\n<b>➙ E-mail:</b> thiengocalopsita@gmail.com

\n\n<b>➙ Contato Blog:</b> https://www.Thiengo.com.br/contato

\n\nÉ isso.

\n\nAbraço.
</string>
...

 

É justamente a String about_channel_full_desc que você modificará para colocar o "porquê" de cada canal cliente que você estiver trabalhando.

Layout

O layout desta tela é bem simples. Primeiro a estrutura deste layout:

Diagrama do layout fragment_about_channel.xml

Agora, no folder /res/layout, crie o layout XML fragment_about_channel.xml com o seguinte código fonte:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.fragment.AboutChannelFragment">

<TextView
style="@style/AppTheme.Title"
android:text="@string/about_channel_content_title" />

<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">

<TextView
android:id="@+id/tv_about_content"
style="@style/AppTheme.TextContent"
android:text="@string/about_channel_full_desc" />
</ScrollView>
</LinearLayout>

 

É possível que você esteja se perguntando:

Thiengo, porque você não utilizou os atributos de scroll direto no TextView que contém o texto principal da tela ao invés de utilizar também um ScrollView?

Simples:

TextView com o scroll habilitado direto nele não é tão responsivo ao toque do usuário quanto quando utilizando também o componente visual ScrollView.

scroll do TextView exige "muito" mais toque em tela para chegar até o final (ou até qualquer outra parte) do conteúdo.

Alias, eu não consigo ver um contexto onde é uma boa escolha utilizar a característica de scroll diretamente no TextView, justamente por causa do problema de resposta ao toque do usuário em tela.

AboutChannelFragment

Agora o simples fragmento da tela Sobre do canal.

Em /ui/fragment adicione a classe AboutChannelFragment com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import thiengo.com.br.canalvinciusthiengo.R

/**
* Contém os dados sobre o canal. Dados em texto
* e mais detalhados sobre a proposta do canal
* YouTube do app.
*
* @constructor cria um objeto completo do tipo
* [AboutChannelFragment].
*/
class AboutChannelFragment : Fragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [AboutChannelFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "AboutChannelFragment_key"
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? ) : View? {

return inflater.inflate(
R.layout.fragment_about_channel,
container,
false
)
}
}

 

Pronto!

Vamos agora ao banco de dados local, Room API. Para depois voltarmos às telas dos outros dois fragmentos. Tela de Último Vídeo Liberado e tela de PlayLists.

Até este ponto do projeto temos a seguinte configuração física (das entidades de código dinâmico) em IDE:

Configuração física do projeto Android

Próximo conteúdo

Aqui conseguimos finalizar com sucesso seis telas de nosso aplicativo.

No próximo conteúdo nós vamos, passo a passo, destrinchar toda a configuração de persistência dinâmica local deste projeto de aplicativo Android.

Segue o link para acesso ao próximo conteúdo:

➙ Banco de Dados Local Com a Room API - Parte 6.

Então é isso.

Descanse um pouco. Veja um vídeo 📽 🎮, tome um café ☕ 🥞. E...

... te vejo na Parte 6 do projeto.

Se houverem dúvidas ou dicas deste quinto conteúdo do aplicativo, então deixe nos comentários que logo eu lhe respondo.

Não esqueça de conhecer também o meu canal no YouTube (caso você ainda não conheça) e...

... não deixe de se inscrever na 📩 lista de e-mails para também garantir a versão em PDF não somente deste projeto de aplicativo Android, mas também de cada novo "conteúdo mini-curso".

Abraço.

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

ViewModel Android, Como Utilizar Este Componente de ArquiteturaViewModel Android, Como Utilizar Este Componente de ArquiteturaAndroid
Lottie API Para Animações no AndroidLottie API Para Animações no AndroidAndroid
Como Impulsionar o App Android - Compartilhamento NativoComo Impulsionar o App Android - Compartilhamento NativoAndroid
Porque e Como Utilizar Vetores no AndroidPorque e Como Utilizar Vetores no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (2)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
21/08/2020
Hey! Just wondering where you got that Android discord link from? The normal link is discord.gg/android, so just curious!

Thanks, nice article :)
Responder
Vinícius Thiengo (0) (0)
22/08/2020
Hi, Jake.

I was just looking for a Discord about Android and Discord app just show me that one.

Have a good one.
Responder