studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones android listview android-listview android-view

android - programacion - ListView con elementos de forma triangular



manual de programacion android pdf (4)

Dos elementos de la lista para una fila y hacer que se pueda hacer clic en el texto.

  • Diseña imágenes para cada fila y dos imágenes para un artículo.
  • Para cada opción, haga clic en el texto de ambos elementos.

Necesito implementar un ListView con elementos de forma triangular como se muestra en esta imagen. Las vistas que uno agrega a un ListView generalmente tienen forma rectangular. Incluso en la documentación, una vista se describe como "ocupa un área rectangular en la pantalla y es responsable del dibujo y manejo de eventos".

¿Cómo puedo agregar formas no rectangulares a ListView y al mismo tiempo asegurarme de que el área de clic esté restringida a la forma, en este caso un triángulo?

¡Gracias!


Mi solución usaría vistas superpuestas que se recortan en triángulos alternos y solo aceptan eventos táctiles dentro de su triángulo.

El problema es que ListView no admite realmente vistas de elementos superpuestos, por lo tanto, mi ejemplo simplemente carga todos los elementos a la vez en ScrollView, lo que puede ser malo si tiene más de, por ejemplo, 30 elementos. Tal vez esto sea factible con un RecyclerView pero no he investigado eso.

He elegido extender el FrameLayout para implementar la lógica de la vista de triángulo, para que pueda usarlo como la vista raíz de un elemento de la lista y poner todo lo que desee en él:

public class TriangleFrameLayout extends FrameLayout { // TODO: constructors public enum Align { LEFT, RIGHT }; private Align alignment = Align.LEFT; /** * Specify whether it''s a left or a right triangle. */ public void setTriangleAlignment(Align alignment) { this.alignment = alignment; } @Override public void draw(Canvas canvas) { // crop drawing to the triangle shape Path mask = new Path(); Point[] tria = getTriangle(); mask.moveTo(tria[0].x, tria[0].y); mask.lineTo(tria[1].x, tria[1].y); mask.lineTo(tria[2].x, tria[2].y); mask.close(); canvas.save(); canvas.clipPath(mask); super.draw(canvas); canvas.restore(); } @Override public boolean onTouchEvent(MotionEvent event) { // check if touch event is within the triangle shape if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { Point touch = new Point((int) event.getX(), (int) event.getY()); Point[] tria = getTriangle(); if (!isPointInsideTrigon(touch, tria[0], tria[1], tria[2])) { // ignore touch event outside triangle return false; } } return super.onTouchEvent(event); } private boolean isPointInsideTrigon(Point s, Point a, Point b, Point c) { // stolen from http://.com/a/9755252 int as_x = s.x - a.x; int as_y = s.y - a.y; boolean s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0; if ((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0 == s_ab) return false; if ((c.x - b.x) * (s.y - b.y) - (c.y - b.y) * (s.x - b.x) > 0 != s_ab) return false; return true; } private Point[] getTriangle() { // define the triangle shape of this View boolean left = alignment == Align.LEFT; Point a = new Point(left ? 0 : getWidth(), -1); Point b = new Point(left ? 0 : getWidth(), getHeight() + 1); Point c = new Point(left ? getWidth() : 0, getHeight() / 2); return new Point[] { a, b, c }; } }

Un ejemplo de diseño XML del elemento, con el TriangleFrameLayout como root, podría verse así:

<?xml version="1.0" encoding="utf-8"?> <your.package.TriangleFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_triangle" android:layout_width="match_parent" android:layout_height="160dp" android:layout_marginTop="-80dp" android:clickable="true" android:foreground="?attr/selectableItemBackground"> <TextView android:id="@+id/item_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="20dp" android:textSize="30dp" android:textStyle="bold" android:textColor="#ffffff" /> </your.package.TriangleFrameLayout>

Aquí tenemos una altura fija de 160dp que puedes cambiar a lo que quieras. Lo importante es el margen superior negativo de la mitad de la altura, -80dp en este caso, que hace que los elementos se superpongan y que los diferentes triángulos coincidan.

Ahora podemos inflar varios de estos elementos y agregarlos a una lista, es decir, ScrollView. Esto muestra un diseño de ejemplo para nuestra Actividad o Framgent:

<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> </LinearLayout> </ScrollView>

Y el código para rellenar la lista:

Aquí creé un adaptador ficticio, análogo a un ListView, que solo enumera nuestros elementos del 0 al 15.

ListAdapter adapter = new BaseAdapter() { @Override public int getCount() { return 16; } @Override public Integer getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View view, ViewGroup parent) { if (view == null) { view = getLayoutInflater().inflate(R.layout.item_tria, parent, false); } // determine whether it''s a left or a right triangle TriangleFrameLayout.Align align = (position & 1) == 0 ? TriangleFrameLayout.Align.LEFT : TriangleFrameLayout.Align.RIGHT; // setup the triangle TriangleFrameLayout triangleFrameLayout = (TriangleFrameLayout) view.findViewById(R.id.root_triangle); triangleFrameLayout.setTriangleAlignment(align); triangleFrameLayout.setBackgroundColor(Color.argb(255, 0, (int) (Math.random() * 256), (int) (Math.random() * 256))); // setup the example TextView TextView textView = (TextView) view.findViewById(R.id.item_text); textView.setText(getItem(position).toString()); textView.setGravity((position & 1) == 0 ? Gravity.LEFT : Gravity.RIGHT); return view; } }; // populate the list LinearLayout list = (LinearLayout) findViewById(R.id.list); for (int i = 0; i < adapter.getCount(); ++i) { final int position = i; // generate the item View View item = adapter.getView(position, null, list); list.addView(item); item.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Toast.makeText(v.getContext(), "#" + position, Toast.LENGTH_SHORT).show(); } }); }

Al final tenemos un resultado que se ve así:


No creo que sea posible crear vistas reales en forma de triángulo y agregarlas a la vista de lista. ¿Y qué diseño utilizarías en tal escenario?

Una forma de hacerlo es usar imágenes de fondo para crear una ilusión . Piense en cada segmento separado por líneas rojas como un elemento en la vista de lista. Por lo tanto, tendrá que crear las imágenes de fondo según sea necesario para cada elemento en la vista de lista y configurarlas en el orden correcto .

Actualización: esto es lo que quiero decir con un corte consistente de la imagen de fondo en mis comentarios a continuación.


Para aquellos que ahora están utilizando RecyclerView , se puede configurar una implementación de ItemDecoration en RecyclerView que:

  1. Elementos de compensación: getItemOffsets() de la decoración. Aquí uno puede mover elementos para que se superpongan.
  2. Dibujar formas: anule onDraw() para dibujar triángulos como fondo.