Lollipop new features: extrayendo colores de una imagen con Palette

jueves, 28 de mayo de 2015


En este artículo vamos a continuar con las novedades de Lollipop, seguimos ampliando la aplicación que empezamos en anteriores artículos. En este artículo vamos a ver como extraer los colores de una imagen utilizando la librería Palette y que uso podemos darle.

Introducción a Palette

Palette es una nueva librería disponible con Lollipop, solo esta disponible en la librería support AppCompat.

Para utilizar Palette es necesario añadir la dependencia Gradle.

dependencies {
  compile 'com.android.support:palette-v7:21.0.0'
}


Para extraer los colores existen dos formas, una síncrona y otra asíncrona.

Métodos síncronos

  • generate(Bitmap)
  • generate(Bitmap, int)
El primer método genera 16 colores por defecto, en el segundo podemos especificar el número de colores que queremos generar, cuantos más colores más tiempo tarda el proceso. El valor de retorno de ambos métodos es un objeto Palette.

Métodos asíncronos

  • generateAsync(Bitmap, PaletteAsyncListener)
  • generateAsync(Bitmap, int, PaletteAsyncListener)
Los métodos son similares, la unica diferencia es que se le pasa a cada método un listener al que se informa cuando con colores se han generado. La implementación de listener es bastante sencilla, contiene un método onGenerated donde recibe el objeto Palette generado.

Palette.PaletteAsyncListener listener = new Palette.PaletteAsyncListener() {
  public void onGenerated(Palette palette) {
    
  }
}


La librería intenta generar 16 colores pero estos son los colores que se suelen usar.
  • Vibrant
  • Vibrant Dark
  • Vibrant Light
  • Muted
  • Muted Dark
  • Muted Light

Ejemplo

Vamos a seguir con nuestra aplicación, en esta ocasión dandole funcionalidad con Palette. Antes de este post he hecho una pequeña refactorización sobre la pantalla de detalle. A nivel visual ahora se muestra un título una descripción del item además de la imagen, y a nivel técnico he dividido el contenido del activity en dos fragments al estilo de lista detalle clásico, de forma que tengo un fragment para visualizar la imagen del item y otro con los datos del item. En portrait la imagen se muestra encima de los datos del item y en landscape a la izquierda.

Cuando utilizamos Palette podemos hacer cosas como establecer el color de fonfo de algún control en base a los colores de la foto que se visualiza en la misma pantalla. Vamos a hacer eso, vamos a colorear el fondo del título del item en función de la paleta y además vamos a mostrar también los 6 colores básicos extraidos de la imagen en una leyenda.

Necesitamos que los dos fragments se comuniquen de forma que el fragment de la imagen cuando esta se haya cargado comunique al fragment de los datos la paleta de colores extraida de la imagen. Vamos a ver los pasos para conseguirlo.

En el fragment de la imagen definimos un interface y un listener, también sobreescribimos el método onAttach para asignar el listener en caso de que el activity lo implemente. Entonces cuando se ha cargado la imagen invocamos el método Palette.generateAsync, cuando la paleta se ha generado invocamos el método onPaletteGenerated del listener.

public class ItemImageFragment extends Fragment {

    .
    .
    .
    OnPaletteGeneratedListener onPaletteGeneratedListener;

    .
    .
    .

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

        Item item = (Item) getActivity().getIntent().getSerializableExtra("item");

        Picasso.with(imageView.getContext())
                .load(item.getImageUrl())
                .into(imageView, new Callback() {
                    @Override
                    public void onSuccess() {

                        Palette.generateAsync(((BitmapDrawable)imageView.getDrawable()).getBitmap(), new Palette.PaletteAsyncListener() {
                            @Override
                            public void onGenerated(Palette palette) {
                                if (onPaletteGeneratedListener != null)
                                    onPaletteGeneratedListener.onPaletteGenerated(palette);
                            }
                        });
                    }

                    @Override
                    public void onError() {

                    }
                });
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (activity instanceof OnPaletteGeneratedListener) {
            onPaletteGeneratedListener = (OnPaletteGeneratedListener) activity;
        }
    }

    public static interface OnPaletteGeneratedListener {
        public void onPaletteGenerated(Palette palette);
    }
}


En el fragment de los datos definimos un método updatePalette donde coloreamos el fondo del título y los colores de la leyenda en base a la paleta de colores recibida.

public class ItemDataFragment extends Fragment {
    TextView titleTextView;
    TextView descriptionTextView;
    TextView vibrantTextView ;
    TextView vibrantDarkTextView;
    TextView vibrantLightTextView;
    TextView mutedTextView;
    TextView mutedDarkTextView;
    TextView mutedLightTextView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {


        View rootView = inflater.inflate(R.layout.fragment_item_data, container, false);
        titleTextView = (TextView)rootView.findViewById(R.id.detailItemTitle);
        descriptionTextView = (TextView)rootView.findViewById(R.id.detailItemDescription);

        //Colors
        vibrantTextView = (TextView)rootView.findViewById(R.id.vibrant_color);
        vibrantDarkTextView = (TextView)rootView.findViewById(R.id.vibrant_dark_color);
        vibrantLightTextView = (TextView)rootView.findViewById(R.id.vibrant_light_color);
        mutedTextView = (TextView)rootView.findViewById(R.id.muted_color);
        mutedDarkTextView = (TextView)rootView.findViewById(R.id.muted_dark_color);
        mutedLightTextView = (TextView)rootView.findViewById(R.id.muted_light_color);

        return rootView;
    }

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

        Item item = (Item) getActivity().getIntent().getSerializableExtra("item");

        titleTextView.setText(item.getTitle());
        descriptionTextView.setText(item.getDescription());
    }

    public void updatePalette(Palette palette){
        int defaultColor = Color.WHITE;

        int titleColor = palette.getVibrantColor(defaultColor);

        titleTextView.setBackgroundColor(titleColor);

        vibrantTextView.setBackgroundColor(palette.getVibrantColor(defaultColor));
        vibrantDarkTextView.setBackgroundColor(palette.getDarkVibrantColor(defaultColor));
        vibrantLightTextView.setBackgroundColor(palette.getLightVibrantColor(defaultColor));
        mutedTextView.setBackgroundColor(palette.getMutedColor(defaultColor));
        mutedDarkTextView.setBackgroundColor(palette.getDarkMutedColor(defaultColor));
        mutedLightTextView.setBackgroundColor(palette.getLightMutedColor(defaultColor));
    }
}


El activity va a hacer de mediador entre los fragments de forma que va a ser el listener del fragment de la imagen y cuando el fragment comunica que se ha generado la paleta, se lo comunicará al fragment de los datos pasandole la paleta generada.

public class ItemDetailActivity  extends ActionBarActivity
                                 implements ItemImageFragment.OnPaletteGeneratedListener {

    private static final String TAG_IMAGE_FRAGMENT = "image_fragment";
    private static final String TAG_DATA_FRAGMENT = "data_fragment";
    ItemImageFragment itemImageFragment;
    ItemDataFragment itemDataFragment;

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

        itemImageFragment = (ItemImageFragment) getSupportFragmentManager().findFragmentByTag(TAG_IMAGE_FRAGMENT);
        itemDataFragment = (ItemDataFragment) getSupportFragmentManager().findFragmentByTag(TAG_DATA_FRAGMENT);

        if (itemImageFragment == null) {
            itemImageFragment = new ItemImageFragment();
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.itemImageContainer, itemImageFragment,TAG_IMAGE_FRAGMENT)
                    .commit();
        }

        if (itemDataFragment == null) {
            itemDataFragment = new ItemDataFragment();

            getSupportFragmentManager().beginTransaction()
                    .add(R.id.itemDataContainer, itemDataFragment,TAG_DATA_FRAGMENT)
                    .commit();
        }
    }

    //OnPaletteGeneratedListener
    @Override
    public void onPaletteGenerated(Palette palette) {
        if (itemDataFragment != null)
            itemDataFragment.updatePalette(palette);
    }
}


Concusiones

Mediante Palette podemos extraer los colores de una imagen y dar una variabilidd al look & feel de nuestra app que en ciertos casos puede ser muy interesante. Hemos visto los sencillo que es implementar esta carácteristica de Lollipop.

Código fuente en GitHub - tag 0.4

Libros relacionados

Android Programming: The Big Nerd Ranch Guide
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming

No hay comentarios:

Publicar un comentario