2016-01-02 35 views
20

enter image description hereListView con triangolare elementi sagomati

devo implementare un ListView con triangolare elementi sagomati come mostrato in questa immagine. Le viste che si aggiungono a un ListView sono generalmente di forma rettangolare. Anche nella documentazione, una vista è descritta come "occupa un'area rettangolare sullo schermo ed è responsabile del disegno e della gestione degli eventi".

Come posso aggiungere forme non rettangolari a ListView e allo stesso tempo assicurarmi che l'area di clic sia limitata alla forma, in questo caso un triangolo.

Grazie!

+1

Questo può essere fatto impostando le immagini di sfondo in una sequenza che crea un'illusione di triangoli. – AndroidMechanic

+0

potresti dirmi dove hai trovato questa immagine di layout? – piotrek1543

+0

@ piotrek1543, Un layout che mi ha dato il mio designer UI/UX. –

risposta

13

La mia soluzione sarebbe utilizzare Visualizzazioni sovrapposte che vengono tagliati a triangoli alternati ed accettare solo gli eventi touch all'interno del suo triangolo.

Il problema è che ListView in realtà non supporta la visualizzazione di voci sovrapposte, quindi il mio esempio carica tutti gli elementi contemporaneamente in una ScrollView, il che può essere negativo se si dispone di più di, diciamo, 30 elementi. Forse questo è fattibile con un RecyclerView ma non l'ho esaminato.

ho scelto di estendere la FrameLayout per implementare la logica triangolo di vista, in modo da poter utilizzare come la radice vista di un elemento della lista e mettere tutto quello che vuoi in esso:

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://stackoverflow.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 XML esempio voce il layout, con il TriangleFrameLayout come root, potrebbe assomigliare a questo:

<?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> 

qui abbiamo un'altezza fissa di 160dp che si può cambiare a tutto ciò che si desidera. La cosa importante è il margine superiore negativo di metà dell'altezza, -80dp in questo caso, che fa sì che gli elementi si sovrappongano e i diversi triangoli coincidano.

Ora possiamo gonfiare più elementi di questo tipo e aggiungerlo a un elenco, ad esempio ScrollView. Questo mostra un esempio di layout per la nostra attività 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> 

E il codice per popolare l'elenco:

Qui ho creato un adattatore fittizio, analogico a un controllo ListView, che appena enumera i nostri articoli da 0 a 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(); 
      } 
     }); 
    } 

alla fine abbiamo un risultato che assomiglia a questo:

enter image description here

+1

Grazie mille @Floern. L'approccio sembra solido. L'unica preoccupazione riguarda la limitazione del numero di articoli. Ad ogni modo, proveremo questo. –

+0

Immagino che questo sia il modo corretto in cui possiamo usare i layout per le nostre esigenze +1 per u @Floern – Hardy

+1

@Floern. Ehi, l'approccio ha funzionato. Potrei ottenere circa 40 articoli senza alcun ritardo sul mio vecchio Nexus 4 con questo approccio. Cercherà di modificarlo per un RecyclerView e verificare se è possibile rimuovere la restrizione del numero di elementi. Molte grazie! –

6

Due voci di elenco per una riga e testo rendere selezionabile.

  • Progettare immagini per ogni riga e due immagini per un articolo.
  • Per ciascuna opzione, è possibile fare solo il testo di entrambi gli elementi selezionabili.

enter image description here

+0

Grazie a @Umar, ma mantenere solo il testo selezionabile non è un'opzione poiché ho bisogno dell'intero triangolo per essere cliccabile. –

+1

@NarayanAcharya Penso che la definizione dell'area cliccabile per una singola riga dovrebbe essere possibile. Date un'occhiata [qui] (http://stackoverflow.com/questions/9489977/is-there-a-way-to-create-a-triangular-button-on-android) – siddhant

2

Non credo che sia possibile creare una vista a forma di vero e proprio triangolo e aggiungerli alla visualizzazione di elenco. E quale layout useresti in uno scenario del genere?

Un modo per farlo è utilizzando le immagini di sfondo per creare un'illusione. Pensa a ciascun segmento separato da linee rosse come elemento nella visualizzazione elenco. Pertanto, è necessario creare le immagini di sfondo come richiesto per ciascun elemento nella visualizzazione elenco e impostarle nell'ordine corretto .

enter image description here

Aggiornamento: Questo è ciò che intendo per affettare coerente dell'immagine di sfondo nei miei commenti qui sotto.

enter image description here

+0

A mio parere, questo è solo uno sfondo creato dall'autore, ma sì è un mix di triangoli. Ho un'idea simile alla tua ;-) – piotrek1543

+0

Grazie a @Virus, questo non mi consente di definire aree di clic a forma triangolare. Lo sfondo singolo –

+0

può causare problemi con l'allineamento dei limiti dell'oggetto e le posizioni previste sugli schermi in caso di ridimensionamento automatico su dispositivi con dimensioni diverse. Non lo è? – AndroidMechanic

1

Per coloro che ora stanno usando RecyclerView, un'implementazione di ItemDecoration può essere impostato su RecyclerView che sarà:

  1. Offset articoli: Override getItemOffsets() di decorazione. Qui si possono spostare gli oggetti in modo che si sovrappongano.
  2. Disegna forme: Ignora onDraw() per disegnare triangoli come sfondo.