Colocando Telas de Introdução em Seu Aplicativo Android

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 /Colocando Telas de Introdução em Seu Aplicativo Android

Colocando Telas de Introdução em Seu Aplicativo Android

Vinícius Thiengo
(13729) (7)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloManual de DevOps: como obter agilidade, confiabilidade e segurança em organizações tecnológicas
CategoriaEngenharia de Software
Autor(es)Gene Kim, Jez Humble, John Willis, Patrick Debois
EditoraAlta Books
Edição
Ano2018
Páginas464
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 trabalhar a funcionalidade de "telas de introdução" em aplicativos Android. Algo que já é comum em muitos apps e essencial em muitos domínios de problema.

Seguindo o princípio do "não reinventar a roda", vamos utilizar uma API de terceiro para a implementação das telas de introdução, API que é muito bem aceita pela comunidade de desenvolvedores Android e tem um suporte excelente as versões do SO no mercado, cobrindo uma fatia de 99.2% dos usuários.

Aqui no artigo essa API será utilizada em um simples aplicativo, social, de perguntas:

Telas de introdução no aplicativo Android Questions?

Vamos utilizar a linguagem Kotlin, logo, se esse é seu primeiro contato com essa linguagem, é importante que estude antes do artigo a seguir: Kotlin Android, Entendendo e Primeiro Projeto.

Antes de prosseguir, não esqueça de se inscrever 📫na lista de e-mails do Blog para receber, em primeira mão, todos os conteúdos de desenvolvimento Android exclusivos aqui do Blog.

Abaixo os tópicos que estaremos abordando:

Atividade de introdução vs ShowCaseScreen vs TapTargets

Uma atividade de introdução pode sim ser utilizada como um tutorial de uso do aplicativo, mas para isso temos a ShowCaseScreen ou ShowCaseView, que é somente para isso: tutorial de uso.

A seguir o exemplo de uma ShowCase:

ShowCase API no Android

A atividade de introdução, ou telas de introdução, é utilizada muitas vezes para apresentar os benefícios do aplicativo e também algumas regras de negócios dele, como por exemplo: a tela de "termos e condições de uso", permitindo o usuário prosseguir somente depois de acordar com os termos do app.

A seguir um exemplo de um slide de uma atividade de introdução:

Slide de atividade de introdução no Android

TapTarget foi introduzida no Android junto ao Material Design e essa é muito similar a ShowCase, podemos até mesmo dizer que ela é a evolução desta última. A TapTarget utiliza todas as definições de layout do Material Design.

Segue um exemplo de uma tap:

Tap API no Android

Resumo até aqui: não há uma opção melhor do que a outra, ambas são úteis e podem ser utilizadas no mesmo aplicativo, exceto a ShowCaseScreen e a TapTarget, não faria sentido ter ambas no mesmo app.

Library de terceiro

Por que dessa vez não será construída a funcionalidade em estudo desde o zero, vulgo: from scratch?

Isso, pois não é viável implementar do zero uma característica que já está pronta partindo de uma API pública da comunidade.

Obviamente que para o uso correto de alguma API devemos primeiro ver qual o tipo de licença em uso e se é compatível com a forma de distribuição de nosso app.

Verificar também se a API é consistente, ou seja, se realmente funciona de acordo com o informado na documentação e se atende as necessidades de nosso aplicativo.

Um bom indício de que a API é boa é o número de estrelas que ela tem no GitHub, até o momento da construção deste artigo eu ainda não tive problemas de consistência e qualidade de APIs quando elas tinham mais do que 1000 estrelas.

De qualquer forma, é muito importante o teste completo da library, isso, pois em alguns casos ela não trabalha como informado, nem mesmo nas issues da API há reclamações sobre o correto funcionamento dela.

No segundo capítulo de meu livro, Receitas Para Desenvolvedores Android, tive de desenvolver do zero a funcionalidade de "Rate App", isso depois de testar as principais APIs públicas e notar que nenhuma delas funcionava como prometido.

No projeto de exemplo deste artigo vamos trabalhar com a API Material-Intro-Screen: https://github.com/TangoAgency/material-intro-screen.

Essa API é muito bem recomendada pela comunidade (número de estrelas, mais do que 1900) e atendeu a todas as características necessárias para o artigo e projeto de exemplo, permitindo até mesmo o trabalho com permissões em tempo de execução.

Ressaltando que aqui trabalharemos somente com atividade de introdução. A instalação e características da API Material-Intro-Screen serão apresentadas no decorrer da configuração desta no projeto de exemplo.

Projeto Android de exemplo

Neste artigo vamos manter a linha de estudo: aplicação da API em foco junto a um projeto de exemplo que simula um aplicativo real, que colocaríamos na Google Play Store.

Dessa vez o projeto é de uma rede mobile de perguntas. Ela não estará funcional, mas será útil para uso de um domínio de problema que realmente ganharia em eficácia caso utilizando uma atividade de introdução.

Para acessar o projeto por completo, digo, até mesmo os arquivos de configuração do Android Studio e imagens, o que não é possível colocar aqui, entre no GitHub dele em: https://github.com/viniciusthiengo/questions-intro-api.

Em seu Android Studio inicie um novo projeto com uma Empty Activity, coloque como nome "Questions?".

Caso esteja com o Android Studio 3+ inicie esse projeto como um Kotlin. Caso contrário, depois de o projeto já ter sido criado, aplique as configurações necessárias para torna-lo um projeto Kotlin.

Ao final dessa primeira parte, sem a API de telas de introdução, teremos o seguinte aplicativo:

Tela principal do aplicativo Android Questions?

E a seguinte estrutura de projeto:

Estrutura física do projeto no Android Studio

Configurações Gradle

A seguir a configuração do Gradle Project Level, ou build.gradle(Project: Questions):

buildscript {
ext.kotlin_version = '1.1.3'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

A cima está em destaque as partes que foram adicionadas, além da configuração inicial de um novo projeto Empty Activity no Android, todas relacionadas ao Kotlin.

Assim a configuração do Gradle App Level, ou build.gradle(Module: app):

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "br.com.thiengo.introapitests"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
testCompile 'junit:junit:4.12'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

/* PARA O RECYCLER VIEW */
compile 'com.android.support:design:25.3.1'

/* IMAGEVIEW CIRCULAR */
compile 'de.hdodenhof:circleimageview:2.1.0'
}
repositories {
mavenCentral()
}

 

Também em destaque, as partes adicionadas devido ao uso do Kotlin, RecyclerView e CircularImageView. Posteriormente voltaremos a essa versão do gradle para adicionar a referência a library Material-Intro-Screen.

Configurações AndroidManifest

O AndroidManifest.xml tem algumas configurações extras, nenhuma ainda relacionada a API Material-Intro-Screen, todas devido ao domínio do problema que estaremos trabalhando. Segue:

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.introapitests">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".QuestionsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>

 

As permissões adicionadas são devido ao domínio do problema, simulando uma regra de negócio, no aplicativo, de que é possível utilizar: tecnologias de obtenção de coordenadas no device e também a câmera dele.

Posteriormente você verá que a API em estudo permite que seja colocada a solicitação dessas permissões em tempo de execução, isso com poucas linhas de código. Lembrando que todas elas são dangerous permissions.

Configurações de estilo

Nossos arquivos XML de configurações de estilo são bem simples. Vamos iniciar com o arquivo de configuração de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#8BC34A</color>
<color name="colorPrimaryDark">#689F38</color>
<color name="colorAccent">#FF5722</color>
</resources>

 

Agora o arquivo de String, /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Questions?</string>
</resources>

 

E por fim o arquivo XML de definição de estilo, /res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">@drawable/background</item>

<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

Classe de domínio

Teremos uma única classe de domínio. No pacote /domain acrescente a classe Kotlin, Question:

class Question(
val userImage: Int,
val question: String
)

 

Como informado: o exemplo é simples. Somente essa classe, com essa estrutura, nos atenderá no projeto de rede mobile de perguntas.

Camada de dados, classe Mock

Mesmo com uma única classe de domínio, preferi manter a separação de conceitos e colocar o código de inicialização do projeto de exemplo, digo, de inicialização de dados, em uma classe de dados simulados, mock data.

No pacote /data acrescente a classe Mock:

class Mock {
companion object{
fun generateQuestionList() = listOf<Question>(
Question(R.drawable.person_1, "O que é possível fazer com o Anko Layouts DSL?"),
Question(R.drawable.person_2, "O que é e para que realmente serve o Gradle?"),
Question(R.drawable.person_3, "Qual das linguagens (em opções) é nativa no Android?"),
Question(R.drawable.person_4, "Qual o valor do novo Volvo ‎XC60?"),
Question(R.drawable.person_5, "Quais foram os primeiros exploradores que chegaram a África?"),
Question(R.drawable.person_6, "Quais as principais montadoras de carros asiáticas?"),
Question(R.drawable.person_7, "Qual o estado brasileiro que protege a Ilha da Trindade?"),
Question(R.drawable.person_8, "Quais são os países participantes da copa das confederações?")
)
}
}

Note que companion object está sendo utilizado para que possamos acessar o método generateQuestionList() sem a necessidade de criação de instância.

Classe adaptadora

Para nossa classe adaptadora, que será utilizada em um RecyclerView, iniciaremos com o layout dela, /res/layout/item_question.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">

<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/iv_user"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
app:civ_border_width="0dp" />

<TextView
android:id="@+id/tv_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_toEndOf="@+id/iv_user"
android:layout_toRightOf="@+id/iv_user"
android:textSize="16sp" />
</RelativeLayout>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout item_question.xml

Assim o código Kotlin da classe QuestionsAdapter que está presente no pacote /adapter:

class QuestionsAdapter(
private val context: Context,
private val questions: List<Question>) :
RecyclerView.Adapter<QuestionsAdapter.ViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int) : QuestionsAdapter.ViewHolder {

val v = LayoutInflater
.from(context)
.inflate(R.layout.item_question, parent, false)
return ViewHolder(v)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setData( questions[position] )
}

override fun getItemCount(): Int {
return questions.size
}

inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var ivUser: CircleImageView
var tvQuestion: TextView

init {
ivUser = itemView.findViewById(R.id.iv_user) as CircleImageView
tvQuestion = itemView.findViewById(R.id.tv_question) as TextView
}

fun setData(question: Question) {
ivUser.setImageResource(question.userImage)
tvQuestion.text = question.question
}
}
}

 

Com exceção do método setData() de ViewHolder, o restante é código padrão que tem de existir em um adapter de RecyclerView.

Atividade principal

Vamos iniciar com o layout de QuestionsActivity, /res/layout/activity_questions.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:background="@android:color/white"
tools:context="br.com.thiengo.introapitests.QuestionsActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_questions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true" />
</RelativeLayout>

 

A seguir o simples diagrama do layout anterior:

Diagrama do layout activity_questions.xml

E assim o algoritmo Kotlin de QuestionsActivity:

class QuestionsActivity : AppCompatActivity() {

val questions = ArrayList<Question>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_questions)

questions.addAll( Mock.generateQuestionList() )
initRecycler()
}

private fun initRecycler() {
rv_questions.setHasFixedSize(true)

val mLayoutManager = LinearLayoutManager(this)
rv_questions.layoutManager = mLayoutManager

val divider = DividerItemDecoration( this, mLayoutManager.orientation)
rv_questions.addItemDecoration(divider)

val adapter = QuestionsAdapter(this, questions)
rv_questions.adapter = adapter
}
}

 

Note que mesmo sabendo que nosso algoritmo trabalha com permissões, digo, elas existem no AndroidManifest.xml, aqui omiti o código de solicitação de permissão em tempo de execução para deixar o exemplo mais leve e também porque a API Material-Intro-Screen nos permiti essa implementação de maneira simples.

Atualizando o aplicativo para trabalho com Atividade de Introdução

Em nosso projeto, como regra de negócio, é importante que os usuários somente consigam acesso a atividade principal depois de terem liberado ao menos as permissões ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION, além de terem acordado com os termos e condições de uso do app.

Depois de atendidas as necessidades iniciais, a atividade de introdução, que conterá também algumas dicas de funcionalidades do app, não mais poderá ser apresentada ao usuário.

A seguir o fluxograma de como deve proceder o algoritmo final que também trabalha com a atividade de introdução:

Fluxograma do funcionamento dos slides no app Questions?

Nossa permissão opcional é a permissão de uso da câmera, android.permission.CAMERA. Nosso último slide não prosseguirá se os termos e condições de uso apresentados não forem acordados pelo usuário.

Para saber se a IntroActivity foi ou não apresentada ao usuário e se ele já acordou com os termos e permissões, utilizaremos um SharedPreferences, pois é uma persistência local, simples e nos atende.

Assim podemos partir para a atualização do projeto.

Atualizando o Gradle App Level

No Gradle App Level, build.gradle (Module: app), adicione a seguinte linha de código, em destaque, para trabalharmos a API Material-Intro-Screen:

...
dependencies {
...

compile 'agency.tango.android:material-intro-screen:0.0.5'
}
...

 

A versão 0.0.5 era a mais atual quando na construção deste artigo. Na documentação tem a recomendação de sempre utilizarmos a versão mais nova, para isso, não deixe de acompanhar, no GitHub da API, a versão mais recente liberada.

Agora é somente sincronizar o projeto. Note que a API Android mínima aceita, minSdkVersion, com essa nova library é a 15, Ice Cream Sandwich.

Criando a atividade de introdução

Nossa atividade de introdução vai ter o nome relativo ao que ela faz, logo, vamos chama-la de IntroActivity, mas você pode nomea-la como quiser, somente lembre que outros programadores estarão lendo seus algoritmos futuramente, é importante que eles sejam auto-comentados.

Segue:

class IntroActivity : MaterialIntroActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}

 

Note que é preciso herdar de MaterialIntroActivity para trabalharmos as características da API adicionada.

Agora vamos atualizar o AndroidManifest.xml para que seja possível abrir primeiro a IntroActivity. Segue atualização:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.introapitests">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".IntroActivity"
android:theme="@style/Theme.Intro">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".QuestionsActivity" />
</application>
</manifest>

 

Outro ponto importante: a atividade de introdução tem de referenciar ao tema Theme.Intro que é inserido junto a API Material-Intro-Screen.

Note que sua atividade de introdução não precisa ser a atividade principal, aqui optamos por esse caminho, pois assim nossa lógica de negócio, discutida anteriormente, é implementada de maneira mais simples.

Ainda não temos nenhum slide definido, digo, nenhum SlideFragment para ser apresentado junto ao ViewPager interno da API. Vamos a eles.

Configuração do primeiro SlideFragment

Nosso primeiro slide que será apresentado é apenas informativo, falaremos de algo que é possível com o aplicativo. Também deixaremos que o usuário obtenha um pouco mais de detalhe caso clique no botão "Quais os tipos de perguntas?".

No onCreate() da IntroActivity adicione o código em destaque:

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

addSlide( SlideFragmentBuilder()
.backgroundColor( R.color.slide_1 )
.buttonsColor( R.color.slide_button )
.image( R.drawable.slide_1 )
.title( resources.getString(R.string.slide_1_title) )
.description( resources.getString(R.string.slide_1_description) )
.build(),
MessageButtonBehaviour( object : View.OnClickListener {
override fun onClick(view: View?) {
showMessage( resources.getString(R.string.slide_1_button_message) )
}
}, resources.getString(R.string.slide_1_button_label)
)
)
}
...

 

Com SlideFragmentBuilder estamos utilizando uma implementação padrão de slide da API em estudo. Posteriormente criaremos um slide customizado.

Todos os métodos são bem intuitivos e, acredite, partindo de um SlideFragmentBuilder somente não trabalhamos ainda os métodos: neededPermissions() e possiblePermissions(). Estes serão utilizados em slides posteriores.

Com exceção de build() em SlideFragmentBuilder, todos os métodos são opcionais, até o argumento MessageButtonBehaviour. Aliás, esse último é utilizado para quando precisamos de alguma ação no slide, ação partindo do clique do usuário em um Button.

Antes de prosseguir, devemos ainda adicionar alguns textos, referentes ao primeiro slide, em /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Questions?</string>

<string name="slide_1_title">Perguntas a comunidade</string>
<string name="slide_1_description">
Escolha a categoria de sua pergunta e então
\n
a libere para a comunidade correta, com
\n
apenas um clique!
</string>
<string name="slide_1_button_label">Quais os tipos de perguntas?</string>
<string name="slide_1_button_message">
De múltiplas escolhas, com: texto, mapa, imagem, vídeo e / ou áudio.
</string>
</resources>

 

E também algumas cores, agora em /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<color name="slide_button">#66000000</color>
<color name="slide_1">#88c441</color>
</resources>

 

Notei que você está utilizando uma imagem local, R.drawable.slide_1, que provavelmente é a imagem de centro do slide. Minha dúvida é sobre o tamanho dessa imagem e se ela deve ser replicada pelos folders drawable-mdpi, hdpi, xhdpi e xxhdpi?

Sim, ela deve ser replicada pelos folders drawable-mdpi, hdpi, xhdpi e xxhdpi. Na documentação não há nada indicando sobre o tamanho ideal da imagem de centro. Nos exemplos presentes lá todos eles são variados.

Em nosso projeto de exemplo utilizei como base o tamanho 275px em mdpi, logo, temos: 275 pixels em mdpi; 413 pixels em hdpi; 550 pixels em xhdpi; e 825 pixels em xxhdpi. Isso para cada imagem de centro de slide.

Antes de prosseguirmos, saiba que o método addSlide() tem uma sobrecarga que somente recebe o primeiro parâmetro, utilizada quando não precisamos de um MessageButtonBehaviour.

Configuração do segundo SlideFragment, com solicitação de permissões obrigatórias em tempo de execução

Antes de adicionarmos os códigos Kotlin do segundo slide, vamos primeiro atualizar o /res/values/strings.xml com as Strings que utilizaremos nesse novo slide:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...

<string name="slide_2_title">Rastreador de membros</string>
<string name="slide_2_description">
Permita, ou não, que pessoas próximas a
\n
você, localmente, recebam suas perguntas
\n
por meio do rastreador de membros.
</string>


<string name="grant_permissions">Liberar permissões</string>
<string name="mis_grant_permissions">Liberar permissões</string>
<string name="please_grant_permissions">
Para poder prosseguir é importante liberar as permissões de localização
</string>
</resources>

 

As definições em português de grant_permissionsmis_grant_permissionsplease_grant_permissions são necessárias, pois a API, como limitação, não oferece suporte a outras línguas senão o inglês.

Colocando nossas próprias traduções para as mesmas Strings references da API, conseguimos sobrescrever o valor primário que está em inglês.

Essa atitude é comum quando encontramos uma API que atende a alguma de nossas necessidades, porém ela ainda não tem a tradução. Somente precisamos identificar o XML de Strings em uso e então traduzi-lo. Acredite, isso é simples e muito útil quando a API é de uma funcionalidade complexa.

Agora vamos a atualização do XML de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<color name="slide_2">#6734b9</color>
</resources>

 

E então, ainda no onCreate() de IntroActivity, adicione os códigos em destaque:

...
override fun onCreate(savedInstanceState: Bundle?) {
...

val neededPermissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION )

addSlide( SlideFragmentBuilder()
.backgroundColor( R.color.slide_2 )
.buttonsColor( R.color.slide_button )
.neededPermissions( neededPermissions )
.image( R.drawable.slide_2 )
.title( resources.getString(R.string.slide_2_title) )
.description( resources.getString(R.string.slide_2_description) )
.build() )
}
...

 

O método neededPermissions() está esperando um array de String, como fizemos utilizando o método arrayOf().

Caso o usuário não dê as permissões solicitadas, a atividade de introdução não permite o acesso ao próximo slide, somente a volta ao slide anterior.

Configuração do terceiro SlideFragment, com solicitação de permissão opcional em tempo de execução

Nosso próximo slide é muito similar ao anterior em termos de regras de negócio, isso, pois ele também trabalha com solicitação de permissão, porém aqui é opcional.

Vamos iniciar com as novas Strings em /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...

<string name="slide_3_title">Fotos e vídeos</string>
<string name="slide_3_description">
Para passar um melhor entendimento, forneça
\n
também nas perguntas algumas fotos e
\n
até mesmo vídeos.
</string>
</resources>

 

E então a nova cor em /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<color name="slide_3">#ff5607</color>
</resources>

 

Assim a atualização no onCreate() da atividade de introdução:

...
override fun onCreate(savedInstanceState: Bundle?) {
...

val possiblePermissions = arrayOf( Manifest.permission.CAMERA )

addSlide( SlideFragmentBuilder()
.backgroundColor( R.color.slide_3 )
.buttonsColor( R.color.slide_button )
.possiblePermissions( possiblePermissions )
.image( R.drawable.slide_3 )
.title( resources.getString(R.string.slide_3_title) )
.description( resources.getString(R.string.slide_3_description) )
.build() )
}
...

 

O Button de solicitação de permissão ainda aparece no slide com o uso de possiblePermissions(), mas o usuário pode prosseguir sem conceder nenhuma permissão, pois elas, neste caso, são opcionais.

Configuração do quarto SlideFragment, slide customizado

Nosso quarto, e último, slide terá: o texto de termos e condições de uso, um título e também um CheckBox de aceitação de termos.

A API Material-Intro-Screen oferece um caminho simples para a construção de slide customizado, criando um fragmento que herde de SlideFragment e então sobrescrevendo os métodos:

  • onCreateView() para ao menos fornecermos nosso próprio layout;
  • canMoveFurther() para fornecermos a lógica de negócio que indicará se o usuário poderá ou não partir para o próximo slide, ou finalizar a atividade de introdução caso esse seja o último slide;
  • cantMoveFurtherErrorMessage() retorna a mensagem que será apresentada em um SnackBar caso o retorno de canMoveFurther() seja false;
  • backgroundColor() para definir a cor de background do slide, pois não poderemos invocar o método backgroundColor() na instância do slide customizado;
  • buttonsColor() para definir a cor dos botões do slide, no caso, o botão para próximo slide, botão para slide anterior e os pontos de posição de slide.

Primeiro vamos aos trechos adicionados em /res/values/strings.xml, trechos deste novo slide:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...

<string name="slide_4_title">Termos e condições de uso</string>
<string name="slide_4_checkbox_label">Concordo com os termos e condições de uso</string>
<string name="slide_4_checkbox_error">
Você precisa concordar com os termos e condições de uso para prosseguir
</string>
<string name="slide_4_content">
Passagem padrão original de Lorem Ipsum, usada desde o século XVI.
\n\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\n\n

Seção 1.10.32 de "de Finibus Bonorum et Malorum", escrita por Cícero em 45 AC
\n\n

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae
vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit
aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit,
sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat
voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit
laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui
in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo
voluptas nulla pariatur?
\n\n

Tradução para o inglês por H. Rackha, feita em 1914
\n\n

But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was
born and I will give you a complete account of the system, and expound the actual teachings of
the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes,
or avoids pleasure itself, because it is pleasure, but because those who do not know how to
pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there
anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because
occasionally circumstances occur in which toil and pain can procure him some great pleasure. To
take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain
some advantage from it? But who has any right to find fault with a man who chooses to enjoy a
pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant
pleasure?
\n\n

Seção 1.10.33 de "de Finibus Bonorum et Malorum", escrita por Cícero em 45 AC
\n\n

At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum
deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non
provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum
fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta
nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis
debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non
recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus
maiores alias consequatur aut perferendis doloribus asperiores repellat.
\n\n

Tradução para o inglês por H. Rackha, feita em 1914
\n\n

On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and
demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee
the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their
duty through weakness of will, which is the same as saying through shrinking from toil and pain.
These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice
is untrammelled and when nothing prevents our being able to do what we like best, every pleasure
is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of
duty or the obligations of business it will frequently occur that pleasures have to be repudiated
and annoyances accepted. The wise man therefore always holds in these matters to this principle of
selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to
avoid worse pains.
</string>
</resources>

 

Assim vamos a atualização do XML de cores, /res/values/colors.xml, para conter a cor de background deste slide:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<color name="slide_4">#5f7c8b</color>
</resources>

 

Agora a apresentação do layout, /res/layout/fragment_terms_conditions_slide.xml:

<?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"
android:padding="16dp"
tools:context="br.com.thiengo.introapitests.QuestionsActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="36dp"
android:text="@string/slide_4_title"
android:textColor="@android:color/white"
android:textSize="26sp" />

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

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/slide_4_content" />
</ScrollView>

<CheckBox
android:id="@+id/cb_concordo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="76dp"
android:text="@string/slide_4_checkbox_label"
android:textColor="@android:color/white" />
</LinearLayout>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout fragment_terms_conditions_slide.xml

E assim o código Kotlin da classe que representa o novo slide, TermsConditionsSlide. No pacote /fragment adicione a classe a seguir:

class TermsConditionsSlide : SlideFragment() {

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

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

override fun canMoveFurther(): Boolean {
if( cb_concordo.isChecked ){
val intent = Intent(activity, QuestionsActivity::class.java)
intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP )
startActivity( intent )
activity.finish()
}
return cb_concordo.isChecked
}

override fun cantMoveFurtherErrorMessage(): String {
return activity.resources.getString(R.string.slide_4_checkbox_error)
}

override fun backgroundColor(): Int {
return R.color.slide_4
}

override fun buttonsColor(): Int {
return R.color.slide_button
}
}

 

Como esse é o código do último slide, caso o usuário tenha concordado com os termos e condições de uso, dado um check na View cb_concordo, em canMoveFurther() podemos invocar a próxima atividade.

Mas note que foi necessária a definição de uma flag, Intent.FLAG_ACTIVITY_CLEAR_TOP, e também a invocação do finish() da IntroActivity.

Isso, pois aparentemente há um bug na API que quando o usuário tenta passar o último slide e a condição de "próximo" é aceita, aqui o CheckBox marcado, o método canMoveFurther() é invocado inúmeras vezes e a IntroActivity não é removida da pilha de atividades neste caso. Comportamento diferente de quando passando para o "próximo" com o clique / touch no next button, onde não precisaríamos nem de flag e nem do método finish().

Estamos acessando diretamente cb_concordo, pois estamos também fazendo uso do plugin kotlin-android-extensions.

Assim podemos seguramente atualizar o onCreate() da IntroActivity:

...
override fun onCreate(savedInstanceState: Bundle?) {
...

addSlide( TermsConditionsSlide() )
}
...

 

Simples, certo?

Agora o que nos resta é o trabalho com o SharedPreferences.

Configuração do SharedPreferences para a lógica da IntroActivity

Primeiro devemos criar uma classe que contenha funções de atualização e obtenção da flag de visualização de atividade de introdução.

No pacote /data adicione a classe SPInfo:

class SPInfo(val context: Context) {

fun updateIntroStatus(status : Boolean){
context
.getSharedPreferences("PREF", Context.MODE_PRIVATE)
.edit()
.putBoolean("status", status)
.apply()
}

fun isIntroShown() = context
.getSharedPreferences("PREF", Context.MODE_PRIVATE)
.getBoolean("status", false)
}

 

Agora precisamos colocar as invocações de updateIntroStatus()isIntroShown() nos locais corretos do projeto. Primeiro vamos a atualização do método canMoveFurther() de TermsConditionsSlide:

...
override fun canMoveFurther(): Boolean {
if( cb_concordo.isChecked ){
SPInfo(activity).updateIntroStatus(true)

val intent = Intent(activity, QuestionsActivity::class.java)
intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP )
startActivity( intent )
activity.finish()
}
return cb_concordo.isChecked
}
...

 

Note que com o Kotlin, o acesso a activity é equivalente ao acesso a getActivity() no Java.

No trecho de código anterior podemos seguramente atualizar o valor de status no SharedPreferences, pois naquele ponto o usuário já aceitou as permissões obrigatórias e também está de acordo com os termos e condições de uso.

Agora a atualização na IntroActivity, isso para quando o usuário entrar no aplicativo sabermos se ele já passou ou não pelos slides de introdução, digo, passou e aceitou as permissões e termos e condições de uso:

class IntroActivity : MaterialIntroActivity() {

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

verifyIntroActivity()
...
}

private fun verifyIntroActivity(){

if( SPInfo(this).isIntroShown() ){
val intent = Intent(this, QuestionsActivity::class.java)
startActivity(intent)
finish()
}
}
}

 

Assim podemos partir para os testes.

Testes e resultados

Depois de dar o rebuild em seu projeto, indo em Menu / Build / Rebuild Project, execute ele no emulador ou device de testes que está a sua disposição. Assim terá a primeira tela do slide (clique em "QUAIS OS TIPOS DE PERGUNTAS"):

Primeiro slide com acionamento de uma SnackBar

No segundo slide, tente passar sem liberar as permissões, terá algo como:

Segundo slide com uma solicitação de permissão obrigatória

Clicando em "LIBERAR PERMISSÕES" temos:

Caixa de diálogo de solicitação de permissão de localização

Com as permissões solicitadas liberadas, podemos seguramente prosseguir para o slide três. Veja que dessa vez podemos passar sem a necessidade de liberar a permission. Mesmo assim, libere ela e prossiga:

Terceiro slide com solicitação de permissão opcional

No quarto e último slide, caso tente prosseguir sem acordar com os termos e condições de uso, não marcando o CheckBox, uma mensagem será apresentada e o "prosseguir" não será possível:

Quarto slide com os Termos e Condições de Uso do aplicativo

Acordando com os termos e prosseguindo, temos:

Slide final e tela inicial do aplicativo Questions?

Com isso seguramente podemos ir a algumas outras características da API.

Parallax

O efeito parallax, resumidamente, é o efeito de mover imagens de fundo de maneira mais lenta do que as imagens de primeiro plano.

Com a API Material-Intro-Screen esse efeito é aplicado aos elementos em tela, e não as imagens de fundo, até porque não temos essa opção, digo, não na interface publica da API.

Veja um exemplo de mudança de slide, veja como o movimento dos elementos, principalmente a imagem e os textos, como não é síncrono, isso além da mudança de cor de background:

Efeito Parallax

Em nosso último slide, onde trabalhamos com um customizado, não foi necessário utilizar nenhuma das tags Parallax, isso, pois não foi enxergada a necessidade desse movimento assíncrono.

Mas caso esse slide estivesse entre outros, você poderia sim manter o efeito nele também, utilizando algumas das Views parallax disponíveis na API:

  • ParallaxFrameLayout;
  • ParallaxLinearLayout;
  • ParallaxRelativeLayout.

Juntamente ao atributo de fator nas Views que receberiam a animação.

Veja o layout a seguir, o /res/layout/fragment_terms_conditions_slide.xml atualizado:

<?xml version="1.0" encoding="utf-8"?>
<agency.tango.materialintroscreen.parallax.ParallaxLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="br.com.thiengo.introapitests.QuestionsActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="36dp"
android:text="@string/slide_4_title"
android:textColor="@android:color/white"
android:textSize="26sp"
app:layout_parallaxFactor="0.6" />

<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:padding="16dp"
app:layout_parallaxFactor="0.1">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/slide_4_content" />
</ScrollView>

<CheckBox
android:id="@+id/cb_concordo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="76dp"
android:text="@string/slide_4_checkbox_label"
android:textColor="@android:color/white"
app:layout_parallaxFactor="0.9" />

</agency.tango.materialintroscreen.parallax.ParallaxLinearLayout>

 

Executando e seguindo ao último slide, temos:

Aplicação do efeito Parallax

Os elementos de menor fator aparecem primeiro, pois os de maior fator são mais rápidos.

Note que para abrir a IntroActivity novamente você terá de: ou remover o aplicativo; ou limpar os dados dele em seu device de testes.

Animação de transição de slides

Caso você queira mudar a animação de alguns elementos do slide, até ele próprio, quando ocorre a transição entre slides, você tem a disposição os seguintes métodos:

  • getNextButtonTranslationWrapper() para o botão de próximo slide;
  • getBackButtonTranslationWrapper() para o botão de slide anterior;
  • getPageIndicatorTranslationWrapper() para o indicador central de slide atual;
  • getViewPagerTranslationWrapper() para o slide completo em si;
  • getSkipButtonTranslationWrapper() para o botão de "pular introdução" que é possível colocar no local do botão de "slide anterior".

Em nosso projeto de exemplo, caso quiséssemos que o botão de "slide anterior" tivesse uma aparição rápida e uma remoção lenta, isso utilizando a animação de fade, poderíamos fazer o seguinte no onCreate() da IntroActivity:

class IntroActivity : MaterialIntroActivity() {

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

verifyIntroActivity()

backButtonTranslationWrapper
.setEnterTranslation {
view, percentage -> view.alpha = percentage * 5
}

...
}
...
}

 

Se você executar o projeto, verá que a apresentação do back button será rápida e a remoção dele, quando voltando do slide atual para o slide anterior, será bem mais lenta que quando não utilizando nosso código de backButtonTranslationWrapper().

Pular button, esconder o back button e transição alpha no último slide

Para colocar o botão de pular, recomendado quando todos os slides da trilha de introdução são opcionais, apenas acrescente o método setSkipButtonVisible() a sua atividade de introdução, exatamente como podemos fazer em nossa IntroActivity:

class IntroActivity : MaterialIntroActivity() {

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

verifyIntroActivity()

setSkipButtonVisible()
...
}
...
}

 

Executando o aplicativo, temos:

Três slides do aplicativo Android Questions?

Onde deveria aparecer o back button para o slide anterior, aparece o skip button.

Em nosso caso, como há restrição de permissão obrigatória e acordo com os termos e condições de uso, o skip button não funciona, digo, ele até tenta finalizar os slides, mas as mensagens de restrição são apresentadas.

Resumo: skip button é somente para quando sua atividade de introdução é opcional, assim o usuário poderá pular direto para a lógica de negócio pós slides, em nosso caso, se não houvesse restrições, seria a invocação da business logic em canMoveFurther() na classe TermsConditionsSlide.

Caso você não queira que o back button seja apresentado, invoque o método hideBackButton() em sua atividade de introdução:

class IntroActivity : MaterialIntroActivity() {

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

verifyIntroActivity()

hideBackButton()
...
}
...
}

 

Assim, temos:

Todos os slides do aplicativo Android Questions?

Note que o uso de hideBackButton() não influencia em nada no funcionamento do "arrastar" do slide, ainda é possível voltar ao slide anterior somente arrastando o atual.

Por fim o método enableLastSlideAlphaExitTransition() que tem como objetivo permitir que o último slide tenha a animação de transição onde ele vai desaparecendo de acordo com o movimento (ou clique) de "próximo". No código, temos:

class IntroActivity : MaterialIntroActivity() {

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

verifyIntroActivity()

enableLastSlideAlphaExitTransition( true )
...
}
...
}

 

E então executando o aplicativo e aplicando o "arrastar para próximo" no último slide, temos:

Transparência de slide não liberado

O problema do uso do método enableLastSlideAlphaExitTransition() com o valor true é que sua restrição de bloqueio, caso o usuário aplique o "arrastar para próximo", é ignorada e nenhuma mensagem de alerta é apresentada, logo, tenha cautela em utilizar essa funcionalidade em slides finais que tenham restrições para poder prosseguir.

Slide com imagem de background

Uma das limitações da API é que não temos uma simples interface pública para a definição de uma imagem de background. Porém, utilizando um custom slide, é possível definirmos os nosso background image.

Primeiro saiba que a imagem de background, caso ela seja definida no View root do layout do slide customizado, deve ter as definições dela ao menos nos quatro principais folders drawable, definições como a seguir:

  • drawable-mdpi: 320x480 pixels;
  • drawable-hdpi: 480x800 pixels;
  • drawable-xhdpi: 640x960 pixels;
  • drawable-xxhdpi: 960x1600 pixels.

Caso queira fornecer uma imagem de background também para os folders ldpi e xxxhdpi, siga os seguintes tamanhos:

  • drawable-ldpi: 240x320 pixels;
  • drawable-xxxhdpi: 1280x1920 pixels.

Em nosso projeto, no slide quatro, temos a possibilidade de utilizarmos uma imagem de background. Atualize o /res/layout/fragment_terms_conditions_slide.xml como abaixo:

<?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:background="@drawable/slide_4_background"
android:orientation="vertical"
android:padding="16dp"
tools:context="br.com.thiengo.introapitests.QuestionsActivity">
...

 

Executando o aplicativo e caminhando até o último slide, temos:

Background personalizado em slide

Assim terminamos nosso projeto proposto junto a explicação e implementação de uma funcionalidade que é comum e útil há vários domínios de problema.

Não esqueça de se inscrever na lista de e-mails 📩 do Blog, logo ao lado ou ao final do artigo.

Se inscreva também no canal: YouTube channel Thiengo Calopsita.

Vídeo com a implementação da API de atividade de introdução

A seguir o vídeo com a implementação passo a passo da API Material-Intro-Screen:

Para acesso ao conteúdo completo do projeto, entre no GitHub a seguir: https://github.com/viniciusthiengo/questions-intro-api.

Conclusão

Como informado inicialmente: dependendo de seu domínio do problema, trabalhar com uma atividade de introdução pode até mesmo ajudar a reter usuários, isso, pois eles saberão logo de início o que será capaz de realizar no app.

Como para qualquer outra funcionalidade, antes de iniciar a codificação do zero, busque primeiro por libraries que já tenham a funcionalidade implementada, libraries bem aceitas na comunidade de desenvolvedores.

É possível que haja limitações na API encontrada, mesmo assim vale o teste (e uso) mesmo que seja ao menos por um curto prazo.

A API que utilizamos no projeto de exemplo da atividade de introdução tem certas limitações que não nos atrapalharam. A principal, e não comentada até aqui, é o "não trabalho com a persistência de slide atual". Ou seja, quando rotacionamos a tela, caso a atividade não esteja travada em uma única orientação, o slide inicial é apresentado novamente.

De qualquer forma, com o domínio de nossos aplicativos, conseguimos implementar uma importante característica, slides de introdução, e ainda colocamos a solicitação de permissões, cruciais ao nosso aplicativo, incluindo o acordo com nossos termos e condições de uso... isso tudo com poucas linhas de código, nos dando mais tempo para focar no business logic do projeto.

Não esqueça de compartilhar e comentar abaixo o que achou além de dar suas dicas de API.

E... inscreva-se na 📩 lista de e-mails do Blog para receber o conteúdo exclusivo e em primeira mão.

Abraço.

Fontes

Documentação oficial da API Material-Intro-Screen

O que é Efeito Parallax? Como funciona?

Documentação oficial da API CircularImageView

Android Dashboards

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

Construindo a Política de Privacidade de Seu Aplicativo Android [Agora Obrigatório]Construindo a Política de Privacidade de Seu Aplicativo Android [Agora Obrigatório]Android
Kotlin Android, Entendendo e Primeiro ProjetoKotlin Android, Entendendo e Primeiro ProjetoAndroid
Iniciando com Anko Kotlin. Intenções no AndroidIniciando com Anko Kotlin. Intenções no AndroidAndroid
Como Utilizar os Operadores IN e Elvis e a API Parcelable no Kotlin AndroidComo Utilizar os Operadores IN e Elvis e a API Parcelable no Kotlin AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (7)

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...
25/02/2019
Fala thiengo muito bom esse post, Eu consigo usar dois ou mais Slides customizados  no caso uns 3 layouts diferentes?
Responder
Vinícius Thiengo (0) (0)
25/02/2019
Luis, tudo bem?

A principio sim, basta seguir criando subclasses de SlideFragment.

Você já tentou isso e está tendo problemas?

Se sim, quais as mensagens de error ou warning estão sendo apresentadas nos logs do Android Studio?

Luis, se ainda não conhece os logs do Android Studio, não deixe de estudar também o conteúdo do link a seguir:

https://developer.android.com/studio/debug/am-logcat?hl=pt-br

Abraço.
Responder
09/06/2018
Thiengo na tela do termo de uso, você utiliza um background diferenciando e para isso você teve que "inflar" uma activity especifica. Estou tentando criar duas activities com backgrounds diferentes. Logo eu teria que criar duas telas seguindo a lógica utilizada da sua tela de termos de uso, todavia, dá erro na hora de rodar o app.

Ainda estou usando Java, não Kotlin.

Segui o código da IntroActivity:

public class IntroActivity extends MaterialIntroActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        enableLastSlideAlphaExitTransition(true);

        getBackButtonTranslationWrapper()
                .setEnterTranslation(new IViewTranslation() {
                    @Override
                    public void translate(View view, @FloatRange(from = 0, to = 1.0) float percentage) {
                        view.setAlpha(percentage);
                    }
                });


        addSlide(new slide1());
        addSlide(new slide2());



    }


________________________________
No Java do slide1

public class slide1 extends SlideFragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.activity_slide1, container, false);
        return view;
    }

}

________________________________
No Java do slide2

public class slide2 extends SlideFragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.activity_slide2, container, false);
        return view;
    }

}

Só que da erro na hora de rodar o app.
Se eu deixar apenas o slide1 no IntroActivity o app roda, mas se eu deixar o slide1 e o slide2 como no código ele não roda.
Entendo que é algum coisa na hora de inflar as activities. Tentei achar algo para inflar multiplas activities, mas não tive sucesso.

Espero que possa ajudar, desde já obrigado.
Responder
Vinícius Thiengo (0) (0)
19/06/2018
Alberto, tudo bem?

Coloque aqui a mensagem (pilha) de erro que aparece nos logs do Android Studio assim que você tenta executar o aplicativo com os slide1 e slide2 configurados para rodar.

Se possível, coloque também a linha de código que a pilha de erros está apontando como problemática.

Abraço.
Responder
Augusto (1) (0)
20/10/2017
Gostei muito do seu tutorial, utilizei no app mas em java, minha duvida e o seguinte antes de utilizar essa forma que achei muito bonita por sinal, eu tinha feito um tratamento para caso a pessoa além de negar a permissão, marcasse em nunca mais exibir a msg ai eu exibia uma msg informando que teria que ir em configurações e liberar e tinha um botão que já levava ate as configs do app, com esse método como eu trato esse ocorrido para uma permissão necessária ?? ja que não achei onde colocar esse teste
Responder
Vinícius Thiengo (0) (0)
23/10/2017
Augusto, tudo bem?

O que é esperado de nós, desenvolvedores Android, é que caso o usuário negue alguma permissão, quando ele for tentar utilizar alguma funcionalidade que necessite da permissão negada, simplesmente devemos plotar a ele um Dialog informando sobre a necessidade e então permitindo que a partir do Dialog a permissão seja liberada (podendo enviar o user para as config do device).

Ou seja, caso a permissão seja negada ainda nas telas introdutórias, deixe o usuário prosseguir mesmo assim, inclusive utilizar o que for possível no aplicativo. Isso para seguir as guidelines do Google Android.

Abraço.
Responder
Heraldo Gama (1) (0)
27/06/2017
TOP 10, simplesmente !!! Muito boa essa API. O vídeo com a qualidade de sempre, tudo muito bem explicado, gostei muito. Parabéns !!!!
Responder