Android/CoordinatorLayout
Què és?
[modifica]Podem per exemple coordinar un fab(Floating action buton) i un snackbar, aconseguint fer que en clicar el botó es desplegui el snackbar i el botó és mogui per tal de fer lloc al snack. El CoordinatorLayout està inclòs en la biblioteca design d'Android que alhora aquesta està continguda en la biblioteca de suport. Per tant, ens veurem forçats a utilitzar l'AppCompatActivity si volem treballar amb CoordinatorLayouts per tal d'aconseguir un efecte més dinàmic en la nostra aplicació.
Material Design
[modifica]El material és la metàfora
En el sentit que s'utilitza una teoria unificadora de l'espai i el sistema de moviment que proporciona. S'inspira en el paper i la tinta però dona pas a multitud d'opcions, i com diu Google, a la màgia.
Afegeixen també la possibilitat de jugar amb les ombres i diferents nivells de superfície amb la qual cosa apareixen relacions en l'espai de les activities que són molt més realistes i alhora atractives.
Atrevit, gràfic i intencional
El nou disseny, segons Google, està basat en la impremta (tipografies, espais, escales, colors i ús d'imatges) que guien el tractament visual dels layouts. Aconsegueixen que sigui un disseny molt més agradable a la vista i permeten una múltiple varietat d'opcions a l'hora de programar una aplicació.
El moviment dona significat
El moviment respecta i reforça la usabilitat de les pantalles d'Android i millora l'experiència d'usuari. La intenció de Google és mostrar una continuïtat del disseny sense que el canvi de pantalles sigui brusc. Tot això sense perdre eficiència ni coherència.Organització de les Views
[modifica]La manera en la qual s'organitzen les Views dintre del CoordinatorLayout és una barreja de l'organització en un Frame Layout i l'organització d'un Relative Layout, ja que podem combinar les principals propietats d'aquest dos tipus de Layouts.
FrameLayout
[modifica]Per defecte un FrameLayout alinea tots els elements a la part superior esquerra del dispositiu, l'un sobre l'altre amb preferència ascendent, és a dir com si enganxéssim un adhesiu sobre un altre (l'últim que enganxem quedarà per sobre dels altres si aquests se superposen).
Com és lògic el FrameLayout ens permet ubicar els elements en diferents posicions gràcies a l'atribut gravity (android:gravity=" ")
Taula amb els possibles valors assignables a la gravity:Constant | Value | Description |
---|---|---|
top | 0x30 | Push object to the top of its container, not changing its size. |
bottom | 0x50 | Push object to the bottom of its container, not changing its size. |
left | 0x03 | Push object to the left of its container, not changing its size. |
right | 0x05 | Push object to the right of its container, not changing its size. |
center_vertical | 0x10 | Place object in the vertical center of its container, not changing its size. |
fill_vertical | 0x70 | Grow the vertical size of the object if needed so it completely fills its container. |
center_horizontal | 0x01 | Place object in the horizontal center of its container, not changing its size. |
fill_horizontal | 0x07 | Grow the horizontal size of the object if needed so it completely fills its container. |
center | 0x11 | Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. |
fill | 0x77 | Grow the horizontal and vertical size of the object if needed so it completely fills its container. |
clip_vertical | 0x80 | Additional option that can be set to have the top and/or bottom edges of the child clipped to its container's bounds. The clip will be based on the vertical gravity: a top gravity will clip the bottom edge, a bottom gravity will clip the top edge, and neither will clip both edges. |
clip_horizontal | 0x08 | Additional option that can be set to have the left and/or right edges of the child clipped to its container's bounds. The clip will be based on the horizontal gravity: a left gravity will clip the right edge, a right gravity will clip the left edge, and neither will clip both edges. |
start | 0x00800003 | Push object to the beginning of its container, not changing its size. |
end | 0x00800005 | Push object to the end of its container, not changing its size. |
RelativeLayout
[modifica]Aquest layout permet especificar la posició de cada element de forma relativa al seu element pare o a qualsevol altre element inclòs en el Layout. D'aquesta manera, en incloure un nou element X podrem indicar per exemple que ha de col·locar-se sota l'element Y i alineat a la dreta del Layout pare.
A més a més també tenim l'opció d'ubicar els elements en funció de la ubicació d'altres gràcies als atributs següents:
Constants | Funció que fan | |
---|---|---|
int | ABOVE | Rule that aligns a child's bottom edge with another child's top edge. |
int | ALIGN_BASELINE | Rule that aligns a child's baseline with another child's baseline. |
int | ALIGN_BOTTOM | Rule that aligns a child's bottom edge with another child's bottom edge. |
int | ALIGN_END | Rule that aligns a child's end edge with another child's end edge. |
int | ALIGN_LEFT | Rule that aligns a child's left edge with another child's left edge. |
int | ALIGN_PARENT_BOTTOM | Rule that aligns the child's bottom edge with its RelativeLayout parent's bottom edge. |
int | ALIGN_PARENT_END | Rule that aligns the child's end edge with its RelativeLayout parent's end edge. |
int | ALIGN_PARENT_LEFT | Rule that aligns the child's left edge with its RelativeLayout parent's left edge. |
int | ALIGN_PARENT_RIGHT | Rule that aligns the child's right edge with its RelativeLayout parent's right edge. |
int | ALIGN_PARENT_START | Rule that aligns the child's start edge with its RelativeLayout parent's start edge. |
int | ALIGN_PARENT_TOP | Rule that aligns the child's top edge with its RelativeLayout parent's top edge. |
int | ALIGN_RIGHT | Rule that aligns a child's right edge with another child's right edge. |
int | ALIGN_START | Rule that aligns a child's start edge with another child's start edge. |
int | ALIGN_TOP | Rule that aligns a child's top edge with another child's top edge. |
int | BELOW | Rule that aligns a child's top edge with another child's bottom edge. |
int | CENTER_HORIZONTAL | Rule that centers the child horizontally with respect to the bounds of its RelativeLayout parent. |
int | CENTER_IN_PARENT | Rule that centers the child with respect to the bounds of its RelativeLayout parent. |
int | CENTER_VERTICAL | Rule that centers the child vertically with respect to the bounds of its RelativeLayout parent. |
int | END_OF | Rule that aligns a child's start edge with another child's end edge. |
int | LEFT_OF | Rule that aligns a child's right edge with another child's left edge. |
int | RIGHT_OF | Rule that aligns a child's left edge with another child's right edge. |
int | START_OF | Rule that aligns a child's end edge with another child's start edge. |
int | TRUE |
RelativeLayout exemple xml:
- Link al repositori: Relative Layout Android
- Anotacions: Xml amb el que aconsseguim montar la imatge de la figura "RelativeLayout exemple".
- Vegeu també: No
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp" >
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reminder" />
<Spinner
android:id="@+id/dates"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/times" />
<Spinner
android:id="@id/times"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_alignParentRight="true" />
<Button
android:layout_width="96dp"
android:layout_height="wrap_content"
android:layout_below="@id/times"
android:layout_alignParentRight="true"
android:text="@string/done" />
</RelativeLayout>
CoordinatorLayout
[modifica]Com s'ha especificat anteriorment l'organització de les Views en un CoordinatorLayout és una barreja de l'organització que utilitza el FrameLayout (ja que podem ubicar els objectes l'un sobre l'altre i sense dependència d'on estiguin els altres ubicats amb l'atribut app:layout_anchorGravity=" " ) però a la vegada, el CoordinatorLayout també té propietats semblants a les d'un RelativeLayout perquè aquest permet ancorar una View a una altra amb la propietat (app:layout_anchor="@id/idview"), gràcies a això es pot aconseguir per exemple ancorar un FAB(FloatingActionButon) a una View, i que quan aquesta es desplaci el FAB es desplaci també.
Implementació
[modifica]Per aclarir el funcionament del CoordinatorLayout a continuació es presenta un exemple d'una petita aplicació que tracta els principals aspectes que ens aporta aquest nou tipus de Layout. El resultat final de l'aplicació serà el mostrat en la figura1.
Com podem observar, l'aplicació reacciona en dues situacions diferents:
- Al fer scroll al NestedScrollView (a mesura que fem scroll cap avall la imatge s'amplia i a la inversa)
- Al fer clic a un Fab o Floating Action Buton" (Quan fem clic en aquest se'ns desplega un Snackbar que va coordinat amb el botó)
Cal tenir en compte que la API que s'ha utilitzat per desenvolupar el codi es la 24. La mínima per utilitzar recursos d'aquest estil és la 21, que coincideix amb Lollipop 5.0.
Dependències
[modifica]El CoordinatorLayout es troba en la biblioteca design.widget que alhora es troba dins de la biblioteca de suport d'Android, obligant-nos a treballar amb l'AppCompatActivity si el que volem fer conté un layout del tipus que estem tractant en aquest wiki. Per lo tant tenim les següents dependències:
- Link al repositori: Pàgina principal d'Android
- Anotacions: Versio 24.2.1 ja que es recomenable utilitzar una versió superior a la 24 per motius de renderització i el jdk 1.8 ja que es necessari per a utilitzar aquesta versió.
- Vegeu també: AppCompatActivity, Android Support Design Widget i Floating Button
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.getbase:floatingactionbutton:1.9.1'
compile 'com.android.support:design:26.1.0'
compile 'com.android.support:appcompat-v7:26.1.0'
}
Java
[modifica]- Link al repositori: Pàgina principal d'Android
- Anotacions: Aquesta plantilla és essencial pel llibre.
- Vegeu també: Android/Layouts predefinits pels adapters i Android/Arquitectura BD SQlite
package materialdesign.example.nak.coordinatorfab;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Hola soc un snackbar", Snackbar.LENGTH_LONG).show();
}
});
}
}
Layout
[modifica]A la figura 5 es pot observar l'estructura del layout utilitzat en l'aplicació d'exemple.
A través d'aquesta simple estructura podem veure fàcilment diferents funcionalitats del CoordinatorLayout.
El layout està format per:
- CoordinatorLayout: És el contenidor de Views que engloba tot i permet aplicar afectes.
- AppBarLayout: És un LinearLayout vertical el qual contindrà tot el que volem que es mostri en el AppBar.
- CollapsingToolbarLayout: És un toolbar especialment pensat per utilitzar amb el CoordinatorLayout, permet que l'App Bar es pugui redimensionar en funció del scroll. Gràcies a l'atribut "app:layout_scrollFlags="scroll|exitUntilCollapsed" aconseguim que aquesta faci scroll fins a estar completament col·lapsada ( com es pot observar a la figura1).
- ImageView: Simplement serveix per mostrar una imatge, en el nostre cas dintre del AppBar.
- Toolbar: Barra superior que normalment s'utilitza per mostrar informació de l'aplicació. Com es pot observar en la figua1 el CollapsingToolbar deixa de fer scroll quan arriba al Toolbar, i passa a visual-se només aquest.
- NestedScrollView: Permet que es pugui fer scroll de les views que conté i alhora avarca tant les versions noves com les velles d'android.
- TextView: una text view clàssica.
- FloatingActionButton: botó flotant d'acció que s'utilitza per a un tipus especial d'acció preeminent. Es distingeixen per una icona d'un cercle flotant per sobre de la interfície d'usuari i tenen comportaments especials de moviment relacionats amb morphing, llançament i el punt d'ancoratge de transferència. Aquest botó, està preparat per treballar amb el CoordinatorLayout i no se li ha d'aplicar cap tipus d'atribut especial perquè es coordini amb el snackbar.
- Link al repositori:
- Anotacions:
- Vegeu també:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/background_03"
/>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
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>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:lineSpacingExtra="8dp"
android:text="@string/lorem"
android:padding="@dimen/activity_horizontal_margin"
android:id="@+id/textView"
/>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:src="@drawable/ic_done_black_24dp"
android:layout_gravity="right|bottom"
android:id="@+id/fab"
/>
</android.support.design.widget.CoordinatorLayout>
Comportament personalitzat
[modifica]Per no aclaparar la wiki amb encara més codi java, s'adjunten les dues funcions principals que fa servir l'autor del codi per a tractar la view. En aquest cas és una CircleImageView. El què desitja fer, és anar movent la imatge cap a la part superior esquerra tal com veiem a la figura 4.
El què fa amb la primera funció presentada (maybeInitProperties) és deduir en quina posició es troba en funció de la view de la qual depèn. En aquest cas és una Toolbar i segons on estigui situada adapta la manera de reduir la CircleImageView. Això ho fa modificant tots els atributs de la classe que ha creat per a tal que les altres funcions després puguin accedir a aquesta informació com en qualsevol altra classe java.
Així doncs en la segona funció el què fa és definir uns punts de canvi que són on la view canviarà de tamany i això ho aplica en funció de com es mogui la Toolbar de la qual depèn.
Un cop definida la classe per al comportament, s'ha d'afegir en el xml per tal que el comportament del nostre layout sigui el que acabem de programar. Per això també s'adjunta aquest fragment de codi xml on es pot veure que es fa servir aquesta classe. S'aprecia en la línia on hi ha una fletxa és on es fa servir la classe que ha creat per a donar el comportament personalitzat a la view que desitjava. Notem també que l'autor treballa amb una modificació del CircleImageView per a tal de poder tractar-la com ell espera que ho faci la funció del comportament. Tot això es el què permet que les Views que veiem en el layout puguin interactuar d'acord amb com s'han programat.
Xml:- Link al repositori: Pàgina del repositori on es troba el codi
- Anotacions:
- Vegeu també:
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="@dimen/image_width"
android:layout_height="@dimen/image_width"
android:layout_gravity="center_horizontal"
android:src="@drawable/quila"
app:border_color="@android:color/white"
app:border_width="2dp"
app:finalHeight="@dimen/image_final_width"
app:finalYPosition="2dp"
app:layout_behavior="saulmm.myapplication.AvatarImageBehavior" <--
app:startHeight="2dp"
app:startToolbarPosition="2dp"
app:startXPosition="2dp"
/>
- Link al repositori: Pàgina del repositori on es troba el codi
- Anotacions:
- Vegeu també:
public AvatarImageBehavior(Context context, AttributeSet attrs) {
mContext = context;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
mCustomFinalYPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalYPosition, 0);
mCustomStartXPosition = a.getDimension(R.styleable.AvatarImageBehavior_startXPosition, 0);
mCustomStartToolbarPosition = a.getDimension(R.styleable.AvatarImageBehavior_startToolbarPosition, 0);
mCustomStartHeight = a.getDimension(R.styleable.AvatarImageBehavior_startHeight, 0);
mCustomFinalHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalHeight, 0);
a.recycle();
}
init();
mFinalLeftAvatarPadding = context.getResources().getDimension(
R.dimen.spacing_normal);
}
*
*
*
private void maybeInitProperties(CircleImageView child, View dependency) {
if (mStartYPosition == 0)
mStartYPosition = (int) (dependency.getY());
if (mFinalYPosition == 0)
mFinalYPosition = (dependency.getHeight() /2);
if (mStartHeight == 0)
mStartHeight = child.getHeight();
if (mStartXPosition == 0)
mStartXPosition = (int) (child.getX() + (child.getWidth() / 2));
if (mFinalXPosition == 0)
mFinalXPosition = mContext.getResources().getDimensionPixelOffset(R.dimen.abc_action_bar_content_inset_material) + ((int) mCustomFinalHeight / 2);
if (mStartToolbarPosition == 0)
mStartToolbarPosition = dependency.getY();
if (mChangeBehaviorPoint == 0) {
mChangeBehaviorPoint = (child.getHeight() - mCustomFinalHeight) / (2f * (mStartYPosition - mFinalYPosition));
}
}
- Link al repositori: Pàgina del repositori on es troba el codi
- Anotacions:
- Vegeu també:
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
maybeInitProperties(child, dependency);
final int maxScrollDistance = (int) (mStartToolbarPosition);
float expandedPercentageFactor = dependency.getY() / maxScrollDistance;
if (expandedPercentageFactor < mChangeBehaviorPoint) {
float heightFactor = (mChangeBehaviorPoint - expandedPercentageFactor) / mChangeBehaviorPoint;
float distanceXToSubtract = ((mStartXPosition - mFinalXPosition)
* heightFactor) + (child.getHeight()/2);
float distanceYToSubtract = ((mStartYPosition - mFinalYPosition)
* (1f - expandedPercentageFactor)) + (child.getHeight()/2);
child.setX(mStartXPosition - distanceXToSubtract);
child.setY(mStartYPosition - distanceYToSubtract);
float heightToSubtract = ((mStartHeight - mCustomFinalHeight) * heightFactor);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (mStartHeight - heightToSubtract);
lp.height = (int) (mStartHeight - heightToSubtract);
child.setLayoutParams(lp);
} else {
float distanceYToSubtract = ((mStartYPosition - mFinalYPosition)
* (1f - expandedPercentageFactor)) + (mStartHeight/2);
child.setX(mStartXPosition - child.getWidth()/2);
child.setY(mStartYPosition - distanceYToSubtract);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (mStartHeight);
lp.height = (int) (mStartHeight);
child.setLayoutParams(lp);
}
return true;
}
Referències i recursos
[modifica]Material Design: https://developer.android.com/design/material/index.html?hl=es
FrameLayout: https://developer.android.com/reference/android/widget/FrameLayout.html
RelativeLayout: https://developer.android.com/reference/android/widget/RelativeLayout.html
CoordinatorLayout: https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html
Aplicació d'exemple CoordinatorLayout sense comportament personalitzat: https://Thauks@bitbucket.org/damo2016/coordinatorfab2.git
Aplicació per explicar el comportament personalitzat: https://github.com/saulmm/CoordinatorBehaviorExample