La libreria Material Design introdujo numerosos elementos destinados a mejorar la interfaz gráfica y hacerla más interactiva y llamativa al usuarios. Uno de estos elementos es el Coordinator Layout, el cual permite realizar animaciones básicas con multitud de efectos y opciones configurables. En código, el coordinator simplemente es el nodo principal de una jerarquía que controla el comportamiento de los elementos que se incluyen en la misma.

Para poder ejecutar las posibilidades del elemento coordinator se realizará sobre un ejemplo básico donde se incluya un AppBarLayout (que permite juntar varios elementos en la barra superior) con un Toolbar y recycler view con adaptador personalizado. Los códigos iniciales serán los siguientes

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

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler"/>

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

    <ImageView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:id="@+id/imagenItem"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/textoItem"
        android:gravity="center_horizontal|center_vertical"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"/>

</LinearLayout>
public class MainActivity extends AppCompatActivity {

    RecyclerView recycler;
    Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recycler = findViewById(R.id.recycler);
        ArrayList lenguajes = new ArrayList();

        lenguajes.add(new Lenguaje("Java", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/java.png"));
        lenguajes.add(new Lenguaje("C#", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/csharp.png"));
        lenguajes.add(new Lenguaje("Python", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/py.png"));
        lenguajes.add(new Lenguaje("Powershell", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/ps.png"));
        lenguajes.add(new Lenguaje("Swift", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/swift.png"));
        lenguajes.add(new Lenguaje("Php", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/php.png"));
        lenguajes.add(new Lenguaje("Kotlin", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/kotlin.png"));

        recycler.setAdapter(new AdaptadorRecycler(lenguajes, getApplicationContext()));
        recycler.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false));
    }
}
public class AdaptadorRecycler extends RecyclerView.Adapter<AdaptadorRecycler.ViewHolder> {

    ArrayList lista;
    Context context;

    public AdaptadorRecycler(ArrayList lista, Context context) {
        this.lista = lista;
        this.context = context;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(context).inflate(R.layout.item_lista,parent,false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

        Lenguaje l = (Lenguaje) lista.get(position);

        holder.nombre.setText(l.getNombre());
        Picasso.with(context).load(l.getImagen()).error(R.drawable.error).placeholder(R.drawable.load).into(holder.logo);
    }

    @Override
    public int getItemCount() {
        return lista.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder{

        TextView nombre;
        ImageView logo;

        public ViewHolder(View itemView) {
            super(itemView);
            nombre = itemView.findViewById(R.id.textoItem);
            logo = itemView.findViewById(R.id.imagenItem);
        }
    }
}
public class Lenguaje {

    String nombre, imagen;

    public Lenguaje(String nombre, String imagen) {
        this.nombre = nombre;
        this.imagen = imagen;
    }

    public String getNombre() {
        return nombre;
    }

    public String getImagen() {
        return imagen;
    }
}
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    implementation 'com.android.support:recyclerview-v7:26.+'
    implementation 'com.squareup.picasso:picasso:2.3.2'
}

Configuraciones básicas

Para poder empezar a trabajar lo necesario es importar la dependencia de la libreria de diseño donde se encuentra el elemento Coordinator en las dependencias del gradle

implementation 'com.android.support:design:26.+'

El siguiente paso es modificar el nodo principal del layout de la activity a Coordinator, junto con un AppBarLayout y un Toolbar

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

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" >

        <android.support.v7.widget.Toolbar
            android:id="@+id/appbar"
            android:layout_height="?attr/actionBarSize"
            android:layout_width="match_parent"
            android:minHeight="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:elevation="4dp"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    </android.support.design.widget.AppBarLayout>


    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler"/>

</LinearLayout>

Lo siguiente es asociar la toolbar a la actividad en el archivo MainActivity.java:

Toolbar toolbar = (Toolbar) findViewById(R.id.appbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("Ejemplo Coordinator");

Con todas estas configuraciones el Coordinator está listo para ser utilizado

Animaciones básicas

Como se ha comentado el coordinator sirve para poder introducir animaciones básicas en elementos de la interfaz. Para eso se utilizan dos atibutos

  • layout_behavior: encargado de escuchar el comportamiento del elemento donde se ponga para actuar sobre los elementos que tengan el atributo scrollFlag
  • layout_scrollFlags: encargado de indicarle al elemento donde se ponga cual será el comportamiento ante determinada situaciones. Las posibilidades de configuración son
    • scroll: el elemento se desaparecerá cuando se produzca un escalo en los elementos que tenga configurado el layout_behavior
    • enterAlways: el elemento aparecerá en el momento que se empiece el scroll en sentido contrario. De no ponerse tan solo aparecerá cuando se realice el scroll al final de la lista

De esta forma el layout de la actividad quedaría de la siguiente forma

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" >

        <android.support.v7.widget.Toolbar
            android:id="@+id/appbar"
            android:layout_height="?attr/actionBarSize"
            android:layout_width="match_parent"
            android:minHeight="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:elevation="4dp"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />

</android.support.design.widget.CoordinatorLayout>

Animaciones con CollapsingToolbarLayout

Uno de las interfaces más utilizadas en las aplicaciones utilizando un modelo de coordinator es el de hacer aparecer y desapareces una parte superior de la interfaz a modo de header con contenido específico. Para poder utilizar este tipo de interfaces es necesario el uso del elemento CollapsingToolbarLayout dentro del AppBarLayout, lque permita que los elementos que están dentro se extiendan o contraigan según el scroll de la pantalla. Por lo tanto la parte superior del xml quedaría de la siguiente forma:

<android.support.design.widget.AppBarLayout
        android:id="@+id/appbarLayout"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctlLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/appbar"
                android:layout_height="?attr/actionBarSize"
                android:layout_width="match_parent"
                android:minHeight="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" >

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

En este caso los elementos son:

  • layout_height del appbarlayout: tamaño máximo que ocupará la parte superior
  • layout_collapseMode =»pin» del Toolbar: que indica que todos los elementos del toolbar (si los hubiese) no se vean afectados en tamaño y transformación por el efecto de animación. Hay que tener en cuenta que en este ejemplo no hay ningún elemento dentro del toolbar
  • layout_scrollFlags desaparece del elemento toolbar ya que no se quiere modificar según se realice el scroll. Sin embargo se mueve al elemento CollapsingToolbar ya que es el que se quiere modificar con el scroll del recycler. Las opciones adicionales son:
    • scroll|enterAlways: la barra collapsing se expanderá en el momento de empezar el scroll
    • scroll|enterAlwaysCollapsed: la barra collapsing se expanderá cuando la lista que evalúa el scroll llegue al principio
    • scroll|enterAlways|enterAlwaysCollapsed: la toolbar aparecerá al hacer scroll en cualquier momento y la collapsing aparecerá cuando se llega al primer elemento del scroll
    • scroll|exitUntilCollapsed: parecida a la anterior con la diferencia que la barra de toolbar se queda fija

[ezcol_1half]scroll|enterAlways[/ezcol_1half] [ezcol_1half_end]scroll|enterAlwaysCollapsed[/ezcol_1half_end]

[ezcol_1half][/ezcol_1half] [ezcol_1half_end][/ezcol_1half_end]

[ezcol_1half]scroll|enterAlways|enterAlwaysCollapsed[/ezcol_1half] [ezcol_1half_end]scroll|exitUntilCollapsed[/ezcol_1half_end]

[ezcol_1half][/ezcol_1half] [ezcol_1half_end][/ezcol_1half_end]

 

[ezcol_2third]Otra característica interesante es la de poner una imagen en el fondo de la parte superior. Para ello se aprovecha la característica de la superposición de capas que presenta CollapsingToolbarLayout poniendo además de la Toolbar un objeto de tipo ImageView con el atributo app:layout_collapseMode=»parallax» para dar efecto de aparición y desaparición[/ezcol_2third] [ezcol_1third_end][/ezcol_1third_end]

<android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctlLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">

            <ImageView
                android:id="@+id/imgToolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/header_coll"
                app:layout_collapseMode="parallax" />

            <android.support.v7.widget.Toolbar
                xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/appbar"
                android:layout_height="?attr/actionBarSize"
                android:layout_width="match_parent"
                android:minHeight="?attr/actionBarSize"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" >

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

[ezcol_2third]Por último un efecto muy interesante y utilizado es la colocación de un floatingbutton entre el header y la parte principal de la lista, el cual desaparecerá / desaparecerá junto con parte superior cuando se haga scroll. Para ello al elemento floating con la característica anchor y anchorGravity en relación al elemento de la barra y al final de la pantalla[/ezcol_2third] [ezcol_1third_end][/ezcol_1third_end]

<android.support.design.widget.FloatingActionButton
        android:id="@+id/botonFl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        app:layout_anchor="@id/appbarLayout"
        app:layout_anchorGravity="bottom|right|end" />

Animaciones con pestañas

Como ya se explico en entradas anteriores, para poder trabajar con pestañas se necesita un elemento TabLayout y un elemento ViewPager. El primero de ellos es el elemento donde irán puesta los nombres de las penstañas y en el segundo los elementos que formar parte de la pestaña correspondiente. En este caso hay que tener en cuenta la jerarquía de animación, estando el TabLayout dentro del Collapsing y el ViewPager fuera pero indicando que sea escuchado el scroll para poder realizar la animación

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/htab_maincontent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/htab_appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/htab_collapse_toolbar"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:titleEnabled="false">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/header_coll"
                android:fitsSystemWindows="true"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/htab_toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:layout_gravity="top"
                android:layout_marginBottom="48dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

            <android.support.design.widget.TabLayout
                android:id="@+id/tabLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"/>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>
public class FragmentPagerAdaptador extends FragmentPagerAdapter {

    ArrayList listaFg;

    public FragmentPagerAdaptador(FragmentManager fm, ArrayList lista) {
        super(fm);
        listaFg = lista;
    }

    @Override
    public Fragment getItem(int position) {
        return (Fragment) listaFg.get(position);
    }

    @Override
    public int getCount() {
        return listaFg.size();
    }

    public CharSequence getPageTitle(int position) {
        Fragment f = (Fragment) listaFg.get(position);
        return f.getClass().toString();
    }
}
public class RecyclerFragment extends Fragment {

    Context c;
    ArrayList lenguajes;
    View v;
    RecyclerView recyclerView;


    public RecyclerFragment() {
        // Required empty public constructor
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        c = context;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        lenguajes = new ArrayList();
        lenguajes.add(new Lenguaje("Java", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/java.png"));
        lenguajes.add(new Lenguaje("C#", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/csharp.png"));
        lenguajes.add(new Lenguaje("Python", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/py.png"));
        lenguajes.add(new Lenguaje("Powershell", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/ps.png"));
        lenguajes.add(new Lenguaje("Swift", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/swift.png"));
        lenguajes.add(new Lenguaje("Php", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/php.png"));
        lenguajes.add(new Lenguaje("Kotlin", "http://developandsys.es/aplicaciones/recursos/logos_lenguajes/kotlin.png"));

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        v = inflater.inflate(R.layout.fragment_lista, container, false);
        return v;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        recyclerView = v.findViewById(R.id.recyclerFg);
        recyclerView.setAdapter(new AdaptadorRecycler(lenguajes, c));
        recyclerView.setLayoutManager(new LinearLayoutManager(c, LinearLayoutManager.VERTICAL, false));

    }
}
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recyclerFg"/>
</LinearLayout>
public class MainActivityCollapTab extends AppCompatActivity {

    FloatingActionButton floatingActionButton;
    ViewPager pager;
    TabLayout tabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_collap_tab);
        pager = findViewById(R.id.pager);
        tabLayout = findViewById(R.id.tabLayout);
        tabLayout.setupWithViewPager(pager);

        ArrayList listaFragments = new ArrayList();
        listaFragments.add(new RecyclerFragment());

        pager.setAdapter(new FragmentPagerAdaptador(getSupportFragmentManager(),listaFragments));

    }
}