Programação para a Plataforma Android – Aula 16 Fragmentos • O que são fragmentos • Como combinar múl?plas visões em uma única tela? • Como lidar com telas de tamanhos variados? • Como modificar a visão de uma a?vidade durante a sua execução? • Como criar caixas de diálogo itera?vas? • Criando uma aplicação para ler livros. Múl?plas a?vidades na tela • Até aqui, sempre trabalhamos com uma a?vidade por tela. – De fato, tecnicamente não é possível combinar múl?plas a?vidades em uma mesma tela. – Por outro lado, isso seria bastante desejável. Por que seria interessante combinarmos d uas atividades em u ma mesma tela? Um Exemplo Você poderia descrever cada uma dessas visões em XML? Visões e Reuso • Em princípio, podemos construir visões como esta à direita em XML. • Porém, se cada uma das visões que a compõem fossem a?vidades separadas, teríamos muito mais reúso. – Porquê? Fragmentos Lembrem-se dessas vantagens, pois vamos discutílas depois. • A fim de separar a?vidades e visões, Android 3.0 (API 11) trouxe o conceito de Fragmentos. • Fragmentos são “quase” a?vidades. • Fragmentos possuem três vantagens: – Aumentam o reúso de a?vidades. – Permitem a construção de telas com múl?plas visões. – Permitem a modificação da visão de uma a?vidade em tempo de execução. Telas de Vários Tamanhos • Fragmentos podem ser programados para adaptar‐se à vários ?pos de telas. Telas de Vários Tamanhos • Desde Android 3.0, temos também infra‐ estrutura para criar vários ?pos de layouts. Caixa de Diálogo Intera?va Caixa de Diálogo: Crie uma caixa de diálogo com dois botões: cancel e ok. • Escolhendo cancel, uma alerta com esse nome deve aparecer na atividade principal. • Escolhendo ok, uma alerta com essa palavra deve ser exibida pela atividade principal. • Como fragmentos são declarados. • Como fragmentos são criados. • Como fragmentos trocam informações com a?vidades. Visão geral da aplicação Por onde começamos? Mas antes de começarmos… • Fragmentos existem somente a par?r de Android 3.0 (API 11). • E mesmo assim, são pacotes na?vos somente a par?r de 4.1 (API 16). • Entre as APIs 11 e 15, fragmentos são uma biblioteca de suporte. – Que pode ser baixada e instalada com o SDK. Layout da A?vidade Principal Como é o layout da atividade principal? Layout da A?vidade Principal ac?vity_main.xml Por agora, isso já devia ser coisa muito simples <?xml version="1.0" encoding="u8‐8"?> <LinearLayout xmlns:android= "h>p://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orienta?on="verEcal" > <Bulon android:id="@+id/bu>on_post_dialog" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@android:string/ok" /> </LinearLayout> E como seria a implementação da atividade? Layout da A?vidade Principal Aula16Ac?vity.xml public class Aula16Ac?vity extends FragmentAc?vity implements OnClickListener { Por agora, isso já devia ser coisa muito simples Existe qualquer diferença para uma atividade normal? @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Bulon bulonPostDialog = (Bulon) findViewById(R.id.bulon_post_dialog); bulonPostDialog.setOnClickListener(this); } public void onClick(View v) { } } Precisamos agora criar o fragmento, e “bolar” um jeito dele “conversar” com a a?vidade. Criando um Fragmento Aula16Ac?vity.xml public void onClick(View v) { Confirma(onDialogFragment confirma?onDialog = Confirma?onDialogFragment.newInstance(R.string.dialog_format_?tle); confirma?onDialog.setConfirma(onDialogFragmentListener(this); confirma?onDialog.show(getSupportFragmentManager(), null); } Temos de implementar essa classe. E dar um jeito da a?vidade e do fragmento conversarem ais Mas, que jeito m um esquisito de criar ão objeto. Por que n r usar um construto padrão? Confirma?onDialogFragment.java Builders public class Confirma?onDialogFragment extends DialogFragment { ... public sta(c Confirma(onDialogFragment newInstance(int (tle) { Confirma(onDialogFragment frag = new Confirma(onDialogFragment(); Bundle args = new Bundle(); args.putInt("(tle", (tle); frag.setArguments(args); return frag; } ... } Parla! • Precisamos de dar um jeito do fragmento e da a?vidade conversarem. • Podemos fazer isso via o padrão observer, um velho conhecido nosso. Quando vimos o padrão observer antes? Ainda os observadores public class Aula16Ac?vity extends FragmentAc?vity implements Aula16Ac?vity.java OnClickListener, Confirma(onDialogFragmentListener { public void onPosi(veClick() { Toast.makeText(this, android.R.string.ok, Toast.LENGTH_LONG).show(); } public void onNega(veClick() { Toast.makeText(this, android.R.string.cancel, Toast.LENGTH_LONG).show(); } public void onClick(View v) { Confirma?onDialogFragment confirma?onDialog = Confirma?onDialogFragment public class Confirma?onDialogFragment extends DialogFragment { .newInstance(R.string.dialog_format_?tle); private Confirma(onDialogFragmentListener listener; confirma?onDialog.setConfirma(onDialogFragmentListener(this); confirma?onDialog.show(getSupportFragmentManager(), null); public interface Confirma(onDialogFragmentListener { } public void onPosi(veClick(); } public void onNega(veClick(); } public void setConfirma(onDialogFragmentListener( Confirma?onDialogFragmentListener listener) { this.listener = listener; } Confirma?onDialogFragment.java } O Observado public class Aula16Ac?vity extends FragmentAc?vity implements OnClickListener, Confirma?onDialogFragmentListener { public void onPosi?veClick() { Toast.makeText(this, android.R.string.ok, Toast.LENGTH_LONG).show(); } public void onNega?veClick() { Toast.makeText(this, android.R.string.cancel, Toast.LENGTH_LONG).show(); } public void onClick(View v) { Confirma?onDialogFragment confirma?onDialog = Confirma?onDialogFragment public class Confirma?onDialogFragment extends DialogFragment { .newInstance(R.string.dialog_format_?tle); private Confirma(onDialogFragmentListener listener; confirma?onDialog.setConfirma?onDialogFragmentListener(this); confirma?onDialog.show(getSupportFragmentManager(), null); public interface Confirma(onDialogFragmentListener { } public void onPosi(veClick(); } public void onNega(veClick(); } public void setConfirma(onDialogFragmentListener( Confirma?onDialogFragmentListener listener) { this.listener = listener; } } O Observador public class Aula16Ac?vity extends FragmentAc?vity implements OnClickListener, Confirma(onDialogFragmentListener { public void onPosi(veClick() { Toast.makeText(this, android.R.string.ok, Toast.LENGTH_LONG).show(); } public void onNega(veClick() { Toast.makeText(this, android.R.string.cancel, Toast.LENGTH_LONG).show(); } public void onClick(View v) { Confirma?onDialogFragment confirma?onDialog = Confirma?onDialogFragment public class Confirma?onDialogFragment extends DialogFragment { .newInstance(R.string.dialog_format_?tle); private Confirma?onDialogFragmentListener listener; confirma?onDialog.setConfirma(onDialogFragmentListener(this); confirma?onDialog.show(getSupportFragmentManager(), null); public interface Confirma?onDialogFragmentListener { } public void onPosi?veClick(); } public void onNega?veClick(); } public void setConfirma?onDialogFragmentListener( Confirma?onDialogFragmentListener listener) { this.listener = listener; } } public class Confirma?onDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { Que padrão de projetos é esse? public void onClick(DialogInterface dialog, int which) { if (listener != null) { switch (which) { case DialogInterface.BUTTON_POSITIVE: E o que a a?vidade listener.onPosi?veClick(); principal fará break; default: quando for listener.onNega?veClick(); no?ficada do evento } que escuta? } } } Confirma?onDialogFragment.java Lidando com o “Clique” @Override public Dialog onCreateDialog(Bundle savedInstanceState) { int ?tle = getArguments().getInt("?tle"); return new AlertDialog.Builder(getAc?vity()) .setIcon(android.R.drawable.ic_dialog_alert).setTitle(?tle) .setPosi?veBulon(android.R.string.ok, this) .setNega?veBulon(android.R.string.cancel, this).create(); } Toast! Aula16Ac?vity.java public class Aula16Ac?vity extends FragmentAc?vity implements OnClickListener, Confirma?onDialogFragmentListener { public void onPosi?veClick() { Toast.makeText(this, android.R.string.ok, Toast.LENGTH_LONG).show(); } public void onNega?veClick() { Toast.makeText(this, android.R.string.cancel, Toast.LENGTH_LONG).show(); } } Recapitulando Aula16Ac?vity.java public class Aula16Ac?vity extends FragmentAc?vity implements OnClickListener, Confirma?onDialogFragmentListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Bulon bulonPostDialog = (Bulon) findViewById(R.id.bulon_post_dialog); bulonPostDialog.setOnClickListener(this); } public void onClick(View v) { Confirma?onDialogFragment confirma?onDialog = Confirma?onDialogFragment .newInstance(R.string.dialog_format_?tle); confirma?onDialog.setConfirma?onDialogFragmentListener(this); confirma?onDialog.show(getSupportFragmentManager(), null); } public void onPosi?veClick() { Toast.makeText(this, android.R.string.ok, Toast.LENGTH_LONG).show(); } public void onNega?veClick() { Toast.makeText(this, android.R.string.cancel, Toast.LENGTH_LONG).show(); } } Confirma?onDialogFragment.java public class Confirma?onDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { private Confirma?onDialogFragmentListener listener; public sta?c Confirma?onDialogFragment newInstance(int ?tle) { Confirma?onDialogFragment frag = new Confirma?onDialogFragment(); Bundle args = new Bundle(); args.putInt("?tle", ?tle); frag.setArguments(args); return frag; } public interface Confirma?onDialogFragmentListener { public void onPosi?veClick(); public void onNega?veClick(); } public void setConfirma?onDialogFragmentListener( Confirma?onDialogFragmentListener listener) { this.listener = listener; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { int ?tle = getArguments().getInt("?tle"); return new AlertDialog.Builder(getAc?vity()) .setIcon(android.R.drawable.ic_dialog_alert).setTitle(?tle) .setPosi?veBulon(android.R.string.ok, this) .setNega?veBulon(android.R.string.cancel, this).create(); } public void onClick(DialogInterface dialog, int which) { if (listener != null) { switch (which) { case DialogInterface.BUTTON_POSITIVE: listener.onPosi?veClick(); break; default: listener.onNega?veClick(); } } } } Múl?plas Visões Leitor de Livros: Crie um leitor de livros com duas visões: • Uma é uma lista com os títulos de capítulso. • A outra é um campo de texto com o conteúdo do capítulo. • Clicando em um título abre-se o capítulo. • Em modo de retrato vê-se somente uma visão de cada vez. • Em modo de paisagem, vêmse as duas! Os Layouts Como é esse layout aqui? E esse layout aqui, como é? Os Layouts Como é esse layout aqui? Na verdade, tudo depende de como estamos vendo o telefone: se em modo de retrato, ou em modo de paisagem… E esse layout aqui, como é? Os Layouts Esse componente de layout, e esse outro serão preenchidos por classes do ?po Fragment. O que são s? esses nome <?xml version="1.0" encoding="uz‐8"?> <LinearLayout xmlns:android= "hlp://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orienta?on="horizontal" > <fragment android:id="@+id/summary_fragment" android:name= "com.dcc052.aula16.SummaryFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/chapter_fragment" android:name= "com.dcc052.aula16.ChapterFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" /> </LinearLayout> Os Layouts Esse outro layout na verdade é somente uma moldura, onde encaixaremos um fragmento em tempo de execução da aplicação. Ele terá, assim duas visões possíveis. <?xml version="1.0" encoding="uz‐8"?> <FrameLayout xmlns:android= "hlp://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" /> ChapterFragment public class ChapterFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) Fragmentos possuem { } @Override um ciclo de vida, public void onStart() parecido, porém { } diferente do ciclo de public void updateChapterView(int posi?on) vida de a?vidades. { } @Override Quais partes public void onSaveInstanceState(Bundle outState) desse ciclo { } seriam iguais? } ChapterFragment.java es E quais part seriam diferentes? Ciclo de Vida Qual é o papel dos métodos abaixo? ‐ onAttach ‐ onCreateView ‐ onActivityCreated ‐ onDestroyView ‐ onDetach ChapterFragment.java Criando Visão do Fragmento public class ChapterFragment extends Fragment { final sta?c String ARG_POSITION = "posi?on"; int mCurrentPosi?on = ‐1; Porque temos esse teste? @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (savedInstanceState != null) { mCurrentPosi?on = savedInstanceState.getInt(ARG_POSITION); } return inflater.inflate(R.layout.chapter_view, container, false); } Esse método faz Que outros } parte do ciclo de vida do Fragmento. métodos são chamados nesse ciclo? Temos de criar esse layout. layout/ChapterView.xml O layout de um capítulo <?xml version="1.0" encoding="uz‐8"?> <TextView xmlns:android= "hlp://schemas.android.com/apk/res/android" android:id="@+id/chapter_view" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:textSize="18sp" /> Por hora o layout será assim. Esse layout Mas é claro que possui algumas poderemos deficiências. melhorá‐lo Quais? depois. Por que não é preciso criar uma versão de paisagem? ChapterFragment.java Começando o Fragmento @Override Explique a public void onStart() { lógica do super.onStart(); condicional nesse código. Bundle args = getArguments(); if (args != null) { updateChapterView(args.getInt(ARG_POSITION)); } else if (mCurrentPosi?on != ‐1) { Precisamos de finir updateChapterView(mCurrentPosi?on); esse método, q ue } cria a visão do } fragmento. @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(ARG_POSITION, mCurrentPosi?on); } Criando a Visão do Fragmento public void updateChapterView(int posi?on) { Ac(vity ac(vity = getAc(vity(); TextView chapter = (TextView) ac?vity.findViewById(R.id.chapter_view); chapter.setText(Book.poems[posi(on]); mCurrentPosi?on = posi?on; O que esse } ade ChapterFragment.java código está fazendo? Que a?vid é essa? • Em termos de reúso de código, esse projeto é um pouco deficiente. – Quais são essas deficiências? Book.java “O Livro” public class Book { • “livro” por hora é um stub. sta?c String[] ?tles = { "Primeiro Poema", "Segundo Poema" }; • Como poderíamos implementar a aplicação defini?va? sta?c String[] poems = { "Olha estas velhas arvores, mais belas\nDo que as …", "Sobre minh'alma, como sobre um trono,\nSenhor..." }; } A lista de capítulos • Nosso próximo passo é implementar a lista de capítulos. • Essa classe deve ser capaz de no?ficar eventos. – Que eventos são esses? SummaryFragment.java public class SummaryFragment extends ListFragment { OnChapterSelectedListener mCallback; public interface OnChapterSelectedListener { public void onChapterSelected(int posi(on); } Para que serve todo esse código ? @Override public void onCreate(Bundle savedInstanceState) { } @Override public void onStart() { } @Override public void onAlach(Ac?vity ac?vity) { } @Override public void onListItemClick(ListView l, View v, int posi?on, long id) { } } Criando o Layout the Lista @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? android.R.layout.simple_list_item_ac?vated_1 : android.R.layout.simple_list_item_1; setListAdapter(new ArrayAdapter<String>(getAc?vity(), layout, Book.?tles)); } SummaryFragment.java Por que ess e teste? Isso se parece com um #ifndef dinâmico. Você pode explicar a analogia? Note que não foi preciso criar o método onCreateView. Porque? Começando a lista de †tulos @Override public void onStart() { super.onStart(); if (getFragmentManager().findFragmentById(R.id.chapter_fragment)!=null) { getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); } } SummaryFragment.java Para que serve esse teste misterioso? Começando a lista de †tulos @Override public void onStart() { super.onStart(); if (getFragmentManager().findFragmentById(R.id.chapter_fragment)!=null) { getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); } } SummaryFragment.java Estamos testando se o fragmento está em modo de tela dupla. Esse só é o caso quando o fragmento chapter_fragment está presente na lista de layouts a?vos. Quando estamos em modo de tela dupla, marcamos o item selecionado na lista. public class SummaryFragment extends ListFragment { OnChapterSelectedListener mCallback; SummaryFragment.java onAlach public interface OnChapterSelectedListener { public void onChapterSelected(int posi?on); Explica‐me } novamente a @Override lógica desse public void onAaach(Ac?vity ac?vity) { código. super.onAlach(ac?vity); try { Precisamos mCallback = (OnChapterSelectedListener) ac?vity; agora implementar a } catch (ClassCastExcep?on e) { a?vidade throw new ClassCastExcep?on(ac?vity.toString() principal. + " must implement OnHeadlineSelectedListener"); } Essa será a cola } que ligará os @Override dois public void onListItemClick(ListView l, View v, int posi?on, long id) { fragmentos. mCallback.onChapterSelected(posi?on); getListView().setItemChecked(posi?on, true); } } A A?vidade Principal Que interface é essa? BookAc?vity.java public class BookAc?vity extends FragmentAc?vity implements SummaryFragment.OnChapterSelectedListener { @Override public void onCreate(Bundle savedInstanceState) { } public void onChapterSelected(int posi?on) { } } Por que esse método está aqui? Como será a implementação de onCreate? Vários Cenários BookAc?vity.java Para que serve esse teste? @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); E esse outro teste, para que setContentView(R.layout.summary_view); ele serve? if (findViewById(R.id.fragment_container) != null) { if (savedInstanceState != null) { return; } SummaryFragment firstFragment = new SummaryFragment(); firstFragment.setArguments(getIntent().getExtras()); getSupportFragmentManager().beginTransac?on() .add(R.id.fragment_container, firstFragment).commit(); } } Transações BookAc?vity.java Fragmentos são criados em transações. Porque? @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Precisamos agora setContentView(R.layout.summary_view); implementar if (findViewById(R.id.fragment_container) != null) { onChapterSelected. Ideas? if (savedInstanceState != null) { return; } SummaryFragment firstFragment = new SummaryFragment(); firstFragment.setArguments(getIntent().getExtras()); getSupportFragmentManager().beginTransac?on() .add(R.id.fragment_container, firstFragment).commit(); } } BookAc?vity.java Selecionando um Capítulo public void onChapterSelected(int posi?on) { ChapterFragment chapterFrag = (ChapterFragment) getSupportFragmentManager() .findFragmentById(R.id.chapter_fragment); if (chapterFrag != null && chapterFrag.getAc(vity() != null) { Para que serve esse teste? chapterFrag.updateChapterView(posi?on); } else { E se eu ChapterFragment newFragment = new ChapterFragment(); removesse essa Bundle args = new Bundle(); condição? args.putInt(ChapterFragment.ARG_POSITION, posi?on); newFragment.setArguments(args); FragmentTransac?on transac?on = getSupportFragmentManager() .beginTransac?on(); transac?on.replace(R.id.fragment_container, newFragment); O que essa transac?on.addToBackStack(null); transação está transac?on.commit(); fazendo? } } Alternando entre visões • É possível alterar a visão do emulador usando a combinação de telcas CTRL+F11. – Será que o custo de alterar essas visões é alto? – Como saber se novas a?vidades são criadas? BookAc?vity.java Logando transições de estado public class BookAc?vity extends FragmentAc?vity implements SummaryFragment.OnChapterSelectedListener { @Override public void onStart() { super.onStart(); Log.v("BookAc?vity", "onStart"); } @Override public void onStop() { super.onStop(); Log.v("BookAc?vity", "onStop"); } @Override public void onResume() { super.onResume(); Log.v("BookAc?vity", "onResume"); } @Override public void onPause() { super.onPause(); Log.v("BookAc?vity", "onPause"); } @Override public void onDestroy() { super.onDestroy(); Log.v("BookAc?vity", "onDestroy"); } } Será que uma nova a?vidade é criada quando alternamos entre retrato e paisagem? Melhorando o Layout de Capítulos • O layout que usamos para capítulos não está bom: o usuário não consegue ler o texto inteiro. O que poderíamos fazer? <?xml version="1.0" encoding="uz‐8"?> <TextView xmlns:android= "hlp://schemas.android.com/apk/res/android" android:id="@+id/chapter_view" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:textSize="18sp" /> chapter_view.xml Melhorando o Layout de Capítulos • O layout que usamos para capítulos não está bom: o usuário não consegue ler o texto inteiro. O que poderíamos fazer? <?xml version="1.0" encoding="uz‐8"?> <TextView xmlns:android= Podemos "hlp://schemas.android.com/apk/res/android" reduzir o android:id="@+id/chapter_view" tamanho da android:layout_width="match_parent" fonte! android:layout_height="match_parent" android:padding="14dp" O que mais android:textSize="18sp" /> podemos chapter_view.xml fazer? chapter_view.xml Melhorando o Layout de Capítulos • O layout que usamos para capítulos não está bom: o usuário não consegue ler o texto inteiro. O que poderíamos fazer? <?xml version="1.0" encoding="uz‐8"?> <ScrollView xmlns:android= "hlp://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="16dp" > <TextView android:id="@+id/chapter_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" /> </ScrollView> Ou podemos usar um scroll