{"id":234,"date":"2017-12-23T10:55:43","date_gmt":"2017-12-23T10:55:43","guid":{"rendered":"http:\/\/vladymix.es\/?p=234"},"modified":"2018-02-22T15:01:39","modified_gmt":"2018-02-22T15:01:39","slug":"lista-agrupada","status":"publish","type":"post","link":"https:\/\/vladymix.es\/wordpress\/index.php\/2017\/12\/23\/lista-agrupada\/","title":{"rendered":"Lista Agrupada"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-236 aligncenter\" src=\"http:\/\/vladymix.es\/wp-content\/uploads\/2017\/12\/GroupList-142x300.png\" alt=\"\" width=\"142\" height=\"300\" \/><\/p>\n<h1>ListView con secciones o\u00a0cabeceras.<\/h1>\n<p>En ocaciones necesitamos visualizar una lista de datos agrupados por alguna caracter\u00edstica en com\u00fan, en el siguiente post ense\u00f1are lo necesario para crear una lista con cabeceras.<\/p>\n<h4>Creamos nuestra actividad.<\/h4>\n<p>En este caso contendr\u00eda un ListView para poder visualizar los datos<\/p>\n<p>ActivityMain.java<\/p>\n<pre class=\"lang:java decode:true\">public class MainActivity extends AppCompatActivity {\r\n\r\n    ListView listView;\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        \r\n        setContentView(R.layout.activity_main);\r\n\r\n        listView = findViewById(R.id.list_view);\r\n\r\n        ArrayList&lt;User&gt; allUsers = Utils.getAllUser();\r\n\r\n        List&lt;Object&gt; listGrouping = Utils.getGroupByName(allUsers);\r\n\r\n        listView.setAdapter(new UserGroupAdapter(MainActivity.this, listGrouping));\r\n\r\n        listView.setOnItemClickListener(this.onItemClick());\r\n\r\n    }\r\n}<\/pre>\n<h4>Creamos nuestro layout<\/h4>\n<p>activity_main.xml<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;RelativeLayout\r\n    xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    tools:context=\"com.example.exaccta.listgrouping.MainActivity\"&gt;\r\n\r\n    &lt;ListView\r\n        android:id=\"@+id\/list_view\"\r\n        android:divider=\"@android:color\/transparent\"\r\n        android:dividerHeight=\"0dp\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\/&gt;\r\n\r\n&lt;\/RelativeLayout&gt;<\/pre>\n<h4>Vamos a definir nuestra cabecera y nuestro item de la lista.<\/h4>\n<p>item_header.xml<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-237\" src=\"http:\/\/vladymix.es\/wp-content\/uploads\/2017\/12\/Captura-de-pantalla-2017-12-23-a-las-11.13.01-300x30.png\" alt=\"\" width=\"300\" height=\"30\" \/><\/p>\n<pre class=\"lang:xhtml decode:true\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;RelativeLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"&gt;\r\n    &lt;TextView\r\n        android:textColor=\"@color\/colorPrimaryDark\"\r\n        android:layout_marginLeft=\"23dp\"\r\n        android:layout_marginRight=\"5dp\"\r\n        android:layout_marginBottom=\"5dp\"\r\n        android:layout_marginTop=\"5dp\"\r\n        android:textStyle=\"bold\"\r\n        android:textSize=\"18sp\"\r\n        android:text=\"A\"\r\n        android:id=\"@+id\/txt_header\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\/&gt;\r\n\r\n    &lt;LinearLayout\r\n        android:background=\"@color\/colorPrimaryDark\"\r\n        android:layout_centerVertical=\"true\"\r\n        android:layout_toRightOf=\"@id\/txt_header\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"1.5dp\"\/&gt;\r\n\r\n&lt;\/RelativeLayout&gt;<\/pre>\n<p>item_content.xml<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-238\" src=\"http:\/\/vladymix.es\/wp-content\/uploads\/2017\/12\/Captura-de-pantalla-2017-12-23-a-las-11.14.45-300x45.png\" alt=\"\" width=\"300\" height=\"45\" \/><\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;RelativeLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"&gt;\r\n\r\n    &lt;RelativeLayout\r\n        android:id=\"@+id\/item_image\"\r\n\r\n        android:padding=\"10dp\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"&gt;\r\n        &lt;ImageView\r\n            android:src=\"@drawable\/ic_circle\"\r\n            android:layout_width=\"40dp\"\r\n            android:layout_height=\"40dp\"\/&gt;\r\n        &lt;TextView\r\n            android:textColor=\"@color\/colorPrimaryDark\"\r\n            android:id=\"@+id\/txt_item_start\"\r\n            android:layout_centerVertical=\"true\"\r\n            android:layout_centerHorizontal=\"true\"\r\n            android:text=\"N\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_height=\"wrap_content\"\/&gt;\r\n    &lt;\/RelativeLayout&gt;\r\n    \r\n    &lt;LinearLayout\r\n        android:layout_toRightOf=\"@+id\/item_image\"\r\n        android:layout_centerVertical=\"true\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"&gt;\r\n        &lt;TextView\r\n            android:id=\"@+id\/txt_name\"\r\n            android:text=\"Name\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_height=\"wrap_content\"\/&gt;\r\n        &lt;TextView\r\n            android:id=\"@+id\/txt_last_name\"\r\n            android:text=\"LastName\"\r\n            android:layout_marginLeft=\"10dp\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_height=\"wrap_content\"\/&gt;\r\n    &lt;\/LinearLayout&gt;\r\n&lt;\/RelativeLayout&gt;<\/pre>\n<p>Bueno hasta aqu\u00ed nada nuevo \u00bfverdad?.<\/p>\n<p>Sigamos.<\/p>\n<h4>Agrupar nuestros items (Lista de usuarios).<\/h4>\n<p>En este caso tenemos varias formas de hacerlo.<\/p>\n<p>1. A\u00f1adiendo una caracter\u00edstica a nuestro objeto para saber si es un item o es una cabecera (is_header).<\/p>\n<pre class=\"lang:java decode:true\">public class User {\r\n\r\n    private String name;\r\n    private String last_name;\r\n    private Boolean is_header;\r\n\r\n    public User(String name, String last_name) {\r\n        this.name = name;\r\n        this.last_name = last_name;\r\n    }\r\n\r\n    public void isHeader(Boolean header){\r\n        this.is_header = header;\r\n    }\r\n\r\n    public String getLast_name() {\r\n        return last_name;\r\n    }\r\n\r\n    public String getName() {\r\n        return name;\r\n    }\r\n\r\n    public void setLast_name(String last_name) {\r\n        this.last_name = last_name;\r\n    }\r\n\r\n    public void setName(String name) {\r\n        this.name = name;\r\n    }\r\n}<\/pre>\n<p>2. Creando un objeto diferente para a\u00f1adirlo a la lista como cabecera.<\/p>\n<pre class=\"lang:java decode:true \">public class Header {\r\n\r\n    private String key;\r\n\r\n    public  Header(String key){\r\n        this.key = key;\r\n    }\r\n\r\n    public String getKey()\r\n    {\r\n        return key;\r\n    }\r\n\r\n    public void setKey(String key)\r\n    {\r\n        this.key = key.substring(0,1);\r\n    }\r\n}<\/pre>\n<p>Aqu\u00ed explicare como usar la segunda opci\u00f3n.<\/p>\n<p>Una vez que tenemos nuestros recursos vamos a codificar un poco.<\/p>\n<h4>Crear una List&lt;Object&gt; agrupada.<\/h4>\n<p>Creamos un m\u00e9todo que nos ayude a agrupar la lista e ir creando la lista agrupada. para ello creamos el m\u00e9todo que reciba una lista de usuarios y los ordene por nombre.<\/p>\n<pre class=\"lang:java decode:true\">public static void orderListByName(ArrayList&lt;User&gt; allUsers){\r\n     Collections.sort(allUsers, new Comparator&lt;User&gt;() {\r\n        @Override public int compare(User o1, User o2) { return o1.getName().compareTo(o2.getName()); } \r\n      });\r\n}<\/pre>\n<p>Ahora como ya tenemos ordenada nuestra lista (por nombre), vamos a recorrerla y por cada nuevo item que encontremos vamos\u00a0 a crear un objeto header<\/p>\n<pre class=\"lang:java decode:true\">List&lt;Object&gt; items= new ArrayList&lt;&gt;();\r\n\r\n        String key = allUsers.get(0).getName().toLowerCase().substring(0,1);\r\n\r\n        items.add(new Header(key));\r\n\r\n        for(User item: allUsers){\r\n            if(item.getName().toLowerCase().startsWith(key)){\r\n                items.add(item);\r\n            }else{\r\n                key = item.getName().toLowerCase().substring(0,1);\r\n                items.add(new Header(key));\r\n                items.add(item);\r\n            }\r\n        }<\/pre>\n<p>De este modo recogemos la clave por la que queremos agrupar, en este caso solo compruebo la primera letra, como nos da igual si es may\u00fascula o min\u00fascula\u00a0 usamos\u00a0toLowerCase() para comparar con min\u00fasculas y comprobar si el siguiente empieza por la letra buscada, si nos fijamos por cada letra nueva que encuentre creamos un objeto Header y a\u00f1adimos a nuestros items agrupados.<\/p>\n<p>El m\u00e9todo completo nos quedaria asi.<\/p>\n<pre class=\"lang:java decode:true\">public static List&lt;Object&gt; getGroupByName(ArrayList&lt;User&gt; allUsers) {\r\n        orderListByName(allUsers);\r\n     \r\n        List&lt;Object&gt; items= new ArrayList&lt;&gt;();\r\n        \/\/ Start group\r\n        String key = allUsers.get(0).getName().toLowerCase().substring(0,1);\r\n\r\n        items.add(new Header(key));\r\n\r\n        for(User item: allUsers){\r\n\r\n            if(item.getName().toLowerCase().startsWith(key)){\r\n                items.add(item);\r\n            }else{\r\n                key = item.getName().substring(0,1);\r\n                \/\/ New group \r\n                items.add(new Header(key));\r\n                items.add(item);\r\n            }\r\n        }\r\n        return items;\r\n    }<\/pre>\n<h4>Creaci\u00f3n de nuestro ArrayAdapter (La parte m\u00e1s compleja).<\/h4>\n<p>Lo mas importante aqu\u00ed es distinguir que tipo de objeto vamos a representar, y en funci\u00f3n de este pintamos una vista u otra.<\/p>\n<p>para usar la opci\u00f3n 1 usaremos esto<\/p>\n<pre class=\"lang:default decode:true\">if(getItem(position).getIsHeader()){\r\n  \/\/ Pintamos el header\r\n}else{\r\n  \/\/ Pintamos el contenido\r\n}<\/pre>\n<p>Como en este ejemplo estamos usando la opci\u00f3n 2<\/p>\n<pre class=\"lang:java decode:true\">if(getItem(position) instanceof Header){\r\n  \/\/ Pintamos el header\r\n}else{\r\n  \/\/ Pintamos el contenido\r\n}<\/pre>\n<p>Tenemos que usar el reciclado de la lista para ello tenemos que comprobar si la vista que vamos a usar es nula o es del tipo header o tipo contenido, para ello tenemos que tener un campo \u00fanico en el item_header e item_contenido para poder distinguirlo<\/p>\n<p>En este caso:<\/p>\n<p>El <strong>item_header<\/strong> tengo un TextView <strong>txt_header<\/strong>\u00a0que no existe en el <span style=\"color: #3366ff;\"><strong>item_contenido<\/strong><\/span> y<\/p>\n<pre class=\"lang:default decode:true\">if (convertView == null || convertView.findViewById(R.id.txt_header)==null)\r\n            {\r\n                convertView = layoutInflater.inflate(R.layout.item_header, parent,false);\r\n            }<\/pre>\n<p>En el <strong>item_contenido<\/strong> tengo un TextView <strong>txt_name<\/strong> que no existe en el <span style=\"color: #3366ff;\"><strong>item_header<\/strong><\/span><\/p>\n<pre class=\"lang:default decode:true \">if (convertView == null || convertView.findViewById(R.id.txt_header)==null)\r\n            {\r\n                convertView = layoutInflater.inflate(R.layout.item_header, parent,false);\r\n            }<\/pre>\n<p>De este modo podemos reciclar la vista actual (View contentview).<\/p>\n<p>UserGroupAdapter.java<\/p>\n<pre class=\"lang:java decode:true\">public class UserGroupAdapter extends ArrayAdapter&lt;Object&gt; {\r\n\r\n    private final LayoutInflater layoutInflater;\r\n\r\n    public UserGroupAdapter(Activity context, List&lt;Object&gt; objects) {\r\n        super(context,0, objects);\r\n        layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\r\n    }\r\n\r\n    @NonNull\r\n    @Override\r\n    public View getView(int position, View convertView, ViewGroup parent)\r\n    {\r\n        \/\/header\r\n        if(getItem(position) instanceof Header)\r\n        {\r\n            if (convertView == null || convertView.findViewById(R.id.txt_header)==null)\r\n            {\r\n                convertView = layoutInflater.inflate(R.layout.item_header, parent,false);\r\n            }\r\n            TextView textView = convertView.findViewById(R.id.txt_header);\r\n            Header header = (Header) getItem(position);\r\n            textView.setText(header.getKey());\r\n        }\r\n        else \/\/ViewHolder Pattern for content\r\n        {\r\n            Holder holder = null;\r\n\r\n            \/\/check if this view contained a header\r\n            if (convertView == null || convertView.findViewById(R.id.txt_name) == null)\r\n            {\r\n                holder = new Holder();\r\n                convertView = layoutInflater.inflate(R.layout.item_content, parent,false);\r\n                holder.setName((TextView) convertView.findViewById(R.id.txt_name));\r\n                holder.setLast_name((TextView) convertView.findViewById(R.id.txt_last_name));\r\n                holder.setTxt_item_start((TextView) convertView.findViewById(R.id.txt_item_start));\r\n\r\n                convertView.setTag(holder);\r\n            }\r\n            else\r\n            {\r\n                holder = (Holder) convertView.getTag();\r\n            }\r\n            User content = (User) getItem(position);\r\n            holder.getTxt_item_start().setText(content.getName().substring(0,1));\r\n            holder.getName().setText(content.getName());\r\n            holder.getLast_name().setText(content.getLast_name());\r\n        }\r\n        return convertView;\r\n    }\r\n\r\n    class Holder\r\n    {\r\n        private TextView name;\r\n        private TextView txt_item_start;\r\n\r\n        private TextView last_name;\r\n\r\n        public TextView getName()\r\n        {\r\n            return name;\r\n        }\r\n\r\n        public TextView getTxt_item_start() {\r\n            return txt_item_start;\r\n        }\r\n\r\n        public void setName(TextView textView)\r\n        {\r\n            this.name = textView;\r\n        }\r\n\r\n        public TextView getLast_name()\r\n        {\r\n            return last_name;\r\n        }\r\n\r\n        public void setLast_name(TextView textView)\r\n        {\r\n            this.last_name = textView;\r\n        }\r\n\r\n        public void setTxt_item_start(TextView txt_item_start) {\r\n            this.txt_item_start = txt_item_start;\r\n        }\r\n    }\r\n}<\/pre>\n<p>Con esto ya estaria todo.<\/p>\n<h3>Efecto desplazamiento.<\/h3>\n<p>En algunas listas que tienen este tipo de contenido agrupado vemos que si presionamos en su header nos pone la el header arriba del todo haciendo un efecto de barrido, esto queda muy chulo, y es una linea de c\u00f3digo que lo hace automaticamente depende del contenido de nuestra lista.<\/p>\n<p>Codificando m\u00e9todo OnItemClickListener del listView<\/p>\n<pre class=\"lang:java decode:true\">listView.setOnItemClickListener(this.onItemClick());<\/pre>\n<p>Yo lo he codificado como un m\u00e9todo fuera para que el c\u00f3digo est\u00e9 un poco m\u00e1s limpio.<\/p>\n<p>En este caso tambi\u00e9n tenemos que distinguir el tipo de objeto. (Ya lo vimos en el apartado de arriba)<\/p>\n<pre class=\"lang:java decode:true\"> private AdapterView.OnItemClickListener onItemClick(){\r\n        return new AdapterView.OnItemClickListener() {\r\n            @Override\r\n            public void onItemClick(AdapterView&lt;?&gt; parent, View view, int position, long id)\r\n            {\r\n                Object item = listView.getAdapter().getItem(position);\r\n                if (item instanceof Header)\r\n                {\r\n                    Toast.makeText(MainActivity.this, ((Header) item).getKey(), Toast.LENGTH_SHORT).show();\r\n                    \/\/ back to header, see\r\n                    \/\/ https:\/\/danielme.com\/tip-android-17-listview-back-to-top-volver-al-inicio\/\r\n                    if (Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.HONEYCOMB)\r\n                    {\r\n                        listView.setSelection(position);\r\n                    }\r\n                    else\r\n                    {\r\n                        listView.smoothScrollToPositionFromTop(position, 0, 300);\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    Toast.makeText(MainActivity.this, ((User) item).getName(), Toast.LENGTH_SHORT).show();\r\n                }\r\n\r\n            }\r\n        };\r\n    }<\/pre>\n<p>Espero que les sirva.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>ListView con secciones o\u00a0cabeceras. En ocaciones necesitamos visualizar una lista de datos agrupados por alguna caracter\u00edstica en com\u00fan, en el&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-234","post","type-post","status-publish","format-standard","hentry","category-android"],"_links":{"self":[{"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/234","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=234"}],"version-history":[{"count":2,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/234\/revisions"}],"predecessor-version":[{"id":257,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/234\/revisions\/257"}],"wp:attachment":[{"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=234"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=234"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vladymix.es\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=234"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}