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:

Questo può essere fatto impostando le immagini di sfondo in una sequenza che crea un'illusione di triangoli. – AndroidMechanic
potresti dirmi dove hai trovato questa immagine di layout? – piotrek1543
@ piotrek1543, Un layout che mi ha dato il mio designer UI/UX. –