Cómo utilizar fragments en Android

Con las primeras versiones para tablet de Android se incluyó una nueva funcionalidad en la API de Google, los fragments. Trozos de la interfaz de usuario con su propio comportamiento y que pueden ser re-utilizados en las pantallas que deseemos. Por ejemplo para hacer un panel multiple al estilo de la app de Gmail o la app de nosinmiubuntu blog.


La siguiente imagen está sacada de la web de android.com, en ella se nos muestra que con la utilización de fragments podemos llegar a conseguir que en una tablet tengamos dos pantallas de listado-detalle y en un móvil se vea en dos pantallas diferentes. Con esto se consigue aprovechar al máximo el espacio disponible según el dispositivo.




Como dije al principio, esta funcionalidad está disponible a partir de la versión 3.0 de Android, lo cual nos dejaría a las versiones 2.x sin ella, salvo que utilicemos la librería ActionBarSherlock . Hoy vamos a explicar el ejemplo que nos proporciona esta en: https://github.com/JakeWharton/ActionBarSherlock/tree/master/samples/fragments

Vamos a empezar por los layouts, necesitaremos dos, se llaman ambos fragment_layout_support.xml, uno va en la carpeta layout y otro en layout-land. Esto es así porque  tendremos dos vistas diferentes dependiendo de la orientación de la pantalla, portrait o landscape.

Esta es la versión de la carpeta layout:


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="aqui.vuestro.espacio.de.nombres.FragmentLayoutSupport$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>



Y esta la de la carpeta layout-land:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="aqui.vuestro.espacio.de.nombres.FragmentLayoutSupport$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

</LinearLayout>



La diferencia entre ambos es el FrameLayout de la versión landscape. También indico en rojo que debeis poner vuestro espacio de nombres en vez del que traía el ejemplo. Justo despues de eso viene el nombre de la Activity que crearemos a continuación.

Ahora vamos a ver como sería la Activity que gestiona nuestros fragments. Tiene alguna diferencia con el original ya que he quitado comentarios y el método setTheme que lo utilizan para cambiar el aspecto general de la app, y que a nosotros aquí no nos hace falta y puede llegar a confundirnos.

Como dije antes se llamará FragmentLayoutSupport y hereda de SherlockFragmentActivity para que podamos tener toda la funcionalidad de los fragments. El método onCreate no tiene ningún problema, hacemos la llamada como siempre a nuestro layout.

Lo que más nos importa en un primer momento son las dos clases que tenemos dentro de la Activity, DetailsActivity y TitlesFragment. La primera verifica en su onCreate la orientación de la pantalla. Si estamos en posición LandScape no se va a utilizar ya que se mostrará al lado del detalle. En caso de utilizarse instanciamos la clase DetailsFragment que veremos más adelante para mostrar el contenido de cualquiera de los items.


public class FragmentLayoutSupport extends SherlockFragmentActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_layout_support);
    }


    /**
     * This is a secondary activity, to show what the user has selected
     * when the screen is not large enough to show it all in one activity.
     */

    public static class DetailsActivity extends SherlockFragmentActivity {

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

            if (getResources().getConfiguration().orientation
                    == Configuration.ORIENTATION_LANDSCAPE) {
                // If the screen is now in landscape mode, we can show the
                // dialog in-line with the list so we don't need this activity.
                finish();
                return;
            }

            if (savedInstanceState == null) {
                // During initial setup, plug in the details fragment.
                DetailsFragment details = new DetailsFragment();
                details.setArguments(getIntent().getExtras());
                getSupportFragmentManager().beginTransaction().add(
                        android.R.id.content, details).commit();
            }
        }
    }


    /**
     * This is the "top-level" fragment, showing a list of items that the
     * user can pick.  Upon picking an item, it takes care of displaying the
     * data to the user as appropriate based on the currrent UI layout.
     */

    public static class TitlesFragment extends SherlockListFragment {
        boolean mDualPane;
        int mCurCheckPosition = 0;

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            // Populate list with our static array of titles.
            setListAdapter(new ArrayAdapter<String>(getActivity(),
                    R.layout.simple_list_item_checkable_1,
                    android.R.id.text1, Shakespeare.TITLES));

            // Check to see if we have a frame in which to embed the details
            // fragment directly in the containing UI.
            View detailsFrame = getActivity().findViewById(R.id.details);
            mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

            if (savedInstanceState != null) {
                // Restore last state for checked position.
                mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
            }

            if (mDualPane) {
                // In dual-pane mode, the list view highlights the selected item.
                getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
                // Make sure our UI is in the correct state.
                showDetails(mCurCheckPosition);
            }
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putInt("curChoice", mCurCheckPosition);
        }

        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            showDetails(position);
        }

        /**
         * Helper function to show the details of a selected item, either by
         * displaying a fragment in-place in the current UI, or starting a
         * whole new activity in which it is displayed.
         */
        void showDetails(int index) {
            mCurCheckPosition = index;

            if (mDualPane) {
                // We can display everything in-place with fragments, so update
                // the list to highlight the selected item and show the data.
                getListView().setItemChecked(index, true);

                // Check what fragment is currently shown, replace if needed.
                DetailsFragment details = (DetailsFragment)
                        getFragmentManager().findFragmentById(R.id.details);
                if (details == null || details.getShownIndex() != index) {
                    // Make new fragment to show this selection.
                    details = DetailsFragment.newInstance(index);

                    // Execute a transaction, replacing any existing fragment
                    // with this one inside the frame.
                    FragmentTransaction ft = getFragmentManager().beginTransaction();
                    ft.replace(R.id.details, details);
                    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                    ft.commit();
                }

            } else {
                // Otherwise we need to launch a new activity to display
                // the dialog fragment with selected text.
                Intent intent = new Intent();
                intent.setClass(getActivity(), DetailsActivity.class);
                intent.putExtra("index", index);
                startActivity(intent);
            }
        }
    }


    /**
     * This is the secondary fragment, displaying the details of a particular
     * item.
     */

    public static class DetailsFragment extends SherlockFragment {
        /**
         * Create a new instance of DetailsFragment, initialized to
         * show the text at 'index'.
         */
        public static DetailsFragment newInstance(int index) {
            DetailsFragment f = new DetailsFragment();

            // Supply index input as an argument.
            Bundle args = new Bundle();
            args.putInt("index", index);
            f.setArguments(args);

            return f;
        }

        public int getShownIndex() {
            return getArguments().getInt("index", 0);
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            if (container == null) {
                // We have different layouts, and in one of them this
                // fragment's containing frame doesn't exist.  The fragment
                // may still be created from its saved state, but there is
                // no reason to try to create its view hierarchy because it
                // won't be displayed.  Note this is not needed -- we could
                // just run the code below, where we would create and return
                // the view hierarchy; it would just never be used.
                return null;
            }

            ScrollView scroller = new ScrollView(getActivity());
            TextView text = new TextView(getActivity());
            int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    4, getActivity().getResources().getDisplayMetrics());
            text.setPadding(padding, padding, padding, padding);
            scroller.addView(text);
            text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
            return scroller;
        }
    }

}



La clase TitlesFragment hereda de SherlockListFragment y nos servirá para mostrar una lista de elementos. Para que empiece a funcionar, si vais a los layouts veréis en el atributo class del fragment que después de hacer referencia a nuestra Activity se añade el simbolo $ y el nombre de la clase TitlesFragment. Y con esto el fragment ya sabe a que clase debe llamar para mostrar su contenido.

Lo siguiente de esta clase será rellenar su ListView mediante los valores que hay en la clase Shakespeare. También se intenta instanciar el elemento details, que como vimos antes va a estar solo en un layout. Si no es nulo el valor donde almacenamos su referencia vamos a tener dos paneles para mostrar.

En esta misma clase un poco más adelante encontramos el método showDetails que nos servirá para mostrar el contenido de cada uno de los items cada vez que pulsemos en un elemento de la lista. En este método se evalúa si estamos en modo mDualPane, es decir, si tenemos dos paneles. En caso de ser un solo panel simplemente se llama a la activity DetailsActivity para mostrar el contenido, y en caso de ser dos se instancia DetailsFragment que pasará a mostrarse en el FrameLayout details de nuestro layout. En ambos casos se les pasa como argumento index para saber de nuestra lista de elementos cual debemos mostrar.

Por último, nos queda explicar la clase DetailsFragment que ya comentamos antes. Simplemente se va encargar de recoger el parámetro index que nos dirá el item que debemos mostrar de la clase Shakespeare.

Su método onCreateView será el encargado de crear y devolver la vista que se verá finalmente. En este caso se crea y devuelve un ScrollView al que se le añade un TextView con el texto de nuestro item.

Durante todo el artículo hemos hablado de la clase Shakespeare. Simplemente es una clase con dos String Arrays, uno para los títulos y otro para los textos. Se podría complicar mucho más el ejemplo descargando datos de un servicio web o leyendo de la base de datos, pero los fragments ya son suficientemente complejos como para en un primer vistazo hacerlo más difícil.


public final class Shakespeare {
    /**
     * Our data, part 1.
     */
    public static final String[] TITLES =
    {
            "Henry IV (1)",
            "Henry V",
            "Henry VIII",
            "Richard II",
            "Richard III",
            "Merchant of Venice",
            "Othello",
            "King Lear"
    };

    /**
     * Our data, part 2.
     */
    public static final String[] DIALOGUE =
    {
            "So shaken as we are, so wan with care," +
            "Find we a time for frighted peace to pant," +
            "And breathe short-winded accents of new broils" +
            "To be commenced in strands afar remote." +
            "No more the thirsty entrance of this soil" +
            "Shall daub her lips with her own children's blood;" +
            "Nor more shall trenching war channel her fields," +
            "Nor bruise her flowerets with the armed hoofs" +
            "Of hostile paces: those opposed eyes," +
            "Which, like the meteors of a troubled heaven," +
            "All of one nature, of one substance bred," +
            "Did lately meet in the intestine shock" +
            "And furious close of civil butchery" +
            "Shall now, in mutual well-beseeming ranks," +
            "March all one way and be no more opposed" +
            "Against acquaintance, kindred and allies:" +
            "The edge of war, like an ill-sheathed knife," +
            "No more shall cut his master. Therefore, friends," +
            "As far as to the sepulchre of Christ," +
            "Whose soldier now, under whose blessed cross" +
            "We are impressed and engaged to fight," +
            "Forthwith a power of English shall we levy;" +
            "Whose arms were moulded in their mothers' womb" +
            "To chase these pagans in those holy fields" +
            "Over whose acres walk'd those blessed feet" +
            "Which fourteen hundred years ago were nail'd" +
            "For our advantage on the bitter cross." +
            "But this our purpose now is twelve month old," +
            "And bootless 'tis to tell you we will go:" +
            "Therefore we meet not now. Then let me hear" +
            "Of you, my gentle cousin Westmoreland," +
            "What yesternight our council did decree" +
            "In forwarding this dear expedience.",

            "Hear him but reason in divinity," +
            "And all-admiring with an inward wish" +
            "You would desire the king were made a prelate:" +
            "Hear him debate of commonwealth affairs," +
            "You would say it hath been all in all his study:" +
            "List his discourse of war, and you shall hear" +
            "A fearful battle render'd you in music:" +
            "Turn him to any cause of policy," +
            "The Gordian knot of it he will unloose," +
            "Familiar as his garter: that, when he speaks," +
            "The air, a charter'd libertine, is still," +
            "And the mute wonder lurketh in men's ears," +
            "To steal his sweet and honey'd sentences;" +
            "So that the art and practic part of life" +
            "Must be the mistress to this theoric:" +
            "Which is a wonder how his grace should glean it," +
            "Since his addiction was to courses vain," +
            "His companies unletter'd, rude and shallow," +
            "His hours fill'd up with riots, banquets, sports," +
            "And never noted in him any study," +
            "Any retirement, any sequestration" +
            "From open haunts and popularity.",

            "I come no more to make you laugh: things now," +
            "That bear a weighty and a serious brow," +
            "Sad, high, and working, full of state and woe," +
            "Such noble scenes as draw the eye to flow," +
            "We now present. Those that can pity, here" +
            "May, if they think it well, let fall a tear;" +
            "The subject will deserve it. Such as give" +
            "Their money out of hope they may believe," +
            "May here find truth too. Those that come to see" +
            "Only a show or two, and so agree" +
            "The play may pass, if they be still and willing," +
            "I'll undertake may see away their shilling" +
            "Richly in two short hours. Only they" +
            "That come to hear a merry bawdy play," +
            "A noise of targets, or to see a fellow" +
            "In a long motley coat guarded with yellow," +
            "Will be deceived; for, gentle hearers, know," +
            "To rank our chosen truth with such a show" +
            "As fool and fight is, beside forfeiting" +
            "Our own brains, and the opinion that we bring," +
            "To make that only true we now intend," +
            "Will leave us never an understanding friend." +
            "Therefore, for goodness' sake, and as you are known" +
            "The first and happiest hearers of the town," +
            "Be sad, as we would make ye: think ye see" +
            "The very persons of our noble story" +
            "As they were living; think you see them great," +
            "And follow'd with the general throng and sweat" +
            "Of thousand friends; then in a moment, see" +
            "How soon this mightiness meets misery:" +
            "And, if you can be merry then, I'll say" +
            "A man may weep upon his wedding-day.",

            "First, heaven be the record to my speech!" +
            "In the devotion of a subject's love," +
            "Tendering the precious safety of my prince," +
            "And free from other misbegotten hate," +
            "Come I appellant to this princely presence." +
            "Now, Thomas Mowbray, do I turn to thee," +
            "And mark my greeting well; for what I speak" +
            "My body shall make good upon this earth," +
            "Or my divine soul answer it in heaven." +
            "Thou art a traitor and a miscreant," +
            "Too good to be so and too bad to live," +
            "Since the more fair and crystal is the sky," +
            "The uglier seem the clouds that in it fly." +
            "Once more, the more to aggravate the note," +
            "With a foul traitor's name stuff I thy throat;" +
            "And wish, so please my sovereign, ere I move," +
            "What my tongue speaks my right drawn sword may prove.",

            "Now is the winter of our discontent" +
            "Made glorious summer by this sun of York;" +
            "And all the clouds that lour'd upon our house" +
            "In the deep bosom of the ocean buried." +
            "Now are our brows bound with victorious wreaths;" +
            "Our bruised arms hung up for monuments;" +
            "Our stern alarums changed to merry meetings," +
            "Our dreadful marches to delightful measures." +
            "Grim-visaged war hath smooth'd his wrinkled front;" +
            "And now, instead of mounting barded steeds" +
            "To fright the souls of fearful adversaries," +
            "He capers nimbly in a lady's chamber" +
            "To the lascivious pleasing of a lute." +
            "But I, that am not shaped for sportive tricks," +
            "Nor made to court an amorous looking-glass;" +
            "I, that am rudely stamp'd, and want love's majesty" +
            "To strut before a wanton ambling nymph;" +
            "I, that am curtail'd of this fair proportion," +
            "Cheated of feature by dissembling nature," +
            "Deformed, unfinish'd, sent before my time" +
            "Into this breathing world, scarce half made up," +
            "And that so lamely and unfashionable" +
            "That dogs bark at me as I halt by them;" +
            "Why, I, in this weak piping time of peace," +
            "Have no delight to pass away the time," +
            "Unless to spy my shadow in the sun" +
            "And descant on mine own deformity:" +
            "And therefore, since I cannot prove a lover," +
            "To entertain these fair well-spoken days," +
            "I am determined to prove a villain" +
            "And hate the idle pleasures of these days." +
            "Plots have I laid, inductions dangerous," +
            "By drunken prophecies, libels and dreams," +
            "To set my brother Clarence and the king" +
            "In deadly hate the one against the other:" +
            "And if King Edward be as true and just" +
            "As I am subtle, false and treacherous," +
            "This day should Clarence closely be mew'd up," +
            "About a prophecy, which says that 'G'" +
            "Of Edward's heirs the murderer shall be." +
            "Dive, thoughts, down to my soul: here" +
            "Clarence comes.",

            "To bait fish withal: if it will feed nothing else," +
            "it will feed my revenge. He hath disgraced me, and" +
            "hindered me half a million; laughed at my losses," +
            "mocked at my gains, scorned my nation, thwarted my" +
            "bargains, cooled my friends, heated mine" +
            "enemies; and what's his reason? I am a Jew. Hath" +
            "not a Jew eyes? hath not a Jew hands, organs," +
            "dimensions, senses, affections, passions? fed with" +
            "the same food, hurt with the same weapons, subject" +
            "to the same diseases, healed by the same means," +
            "warmed and cooled by the same winter and summer, as" +
            "a Christian is? If you prick us, do we not bleed?" +
            "if you tickle us, do we not laugh? if you poison" +
            "us, do we not die? and if you wrong us, shall we not" +
            "revenge? If we are like you in the rest, we will" +
            "resemble you in that. If a Jew wrong a Christian," +
            "what is his humility? Revenge. If a Christian" +
            "wrong a Jew, what should his sufferance be by" +
            "Christian example? Why, revenge. The villany you" +
            "teach me, I will execute, and it shall go hard but I" +
            "will better the instruction.",

            "Virtue! a fig! 'tis in ourselves that we are thus" +
            "or thus. Our bodies are our gardens, to the which" +
            "our wills are gardeners: so that if we will plant" +
            "nettles, or sow lettuce, set hyssop and weed up" +
            "thyme, supply it with one gender of herbs, or" +
            "distract it with many, either to have it sterile" +
            "with idleness, or manured with industry, why, the" +
            "power and corrigible authority of this lies in our" +
            "wills. If the balance of our lives had not one" +
            "scale of reason to poise another of sensuality, the" +
            "blood and baseness of our natures would conduct us" +
            "to most preposterous conclusions: but we have" +
            "reason to cool our raging motions, our carnal" +
            "stings, our unbitted lusts, whereof I take this that" +
            "you call love to be a sect or scion.",

            "Blow, winds, and crack your cheeks! rage! blow!" +
            "You cataracts and hurricanoes, spout" +
            "Till you have drench'd our steeples, drown'd the cocks!" +
            "You sulphurous and thought-executing fires," +
            "Vaunt-couriers to oak-cleaving thunderbolts," +
            "Singe my white head! And thou, all-shaking thunder," +
            "Smite flat the thick rotundity o' the world!" +
            "Crack nature's moulds, an germens spill at once," +
            "That make ingrateful man!"
    };
}



Por último nos falta definir nuestra activity en el AndroidManifest.xml, en este caso de una forma un poco diferente al resto de ocasiones:


 <activity android:label="@string/fragment_layout_support" android:name=".FragmentLayoutSupport" />
 <activity android:name=".FragmentLayoutSupport$DetailsActivity"/>

Comments are closed.