带有三角形项目的 ListView
ListView with triangular shaped items
我需要实现一个带有三角形项目的 ListView,如图所示。添加到 ListView 的视图通常是矩形的。即使在文档中,视图也被描述为 "occupies a rectangular area on the screen and is responsible for drawing and event handling"。
如何将非矩形形状添加到 ListView 并同时确保点击区域限于形状,在本例中为三角形。
谢谢!
一行的两个列表项,并使文本可点击。
- 每行设计图片,每行设计两张图片。
- 对于每个选项,仅使两个项目的文本可点击。
我认为不可能创建实际的三角形视图并将它们添加到列表视图中。在这种情况下你会使用什么布局?
一种方法是使用背景图像来创造幻觉。将由红线分隔的每个段视为列表视图中的一个项目。因此,您必须根据需要为列表视图中的每个项目创建背景图像,并以正确的顺序设置它们。
更新:这就是我在下面评论中对背景图片进行一致切片的意思。
我的解决方案将使用重叠的视图,这些视图被裁剪成交替的三角形,并且只接受其三角形内的触摸事件。
问题是 ListView 并不真正支持重叠的项目视图,因此我的示例只是将所有项目一次加载到 ScrollView 中,如果您有超过 30 个项目,这可能会很糟糕。也许这对于 RecyclerView 是可行的,但我还没有研究过。
我选择扩展 FrameLayout 来实现三角形 View 逻辑,因此您可以将它用作列表项的根 View,并在其中放入任何您想要的东西:
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
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 };
}
}
以 TriangleFrameLayout
为根的项目 XML 布局示例可能如下所示:
<?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>
这里我们有一个固定的高度160dp
,您可以随意更改。重要的是一半高度的负上边距,-80dp
在这种情况下,这会导致项目重叠并且不同的三角形匹配。
现在我们可以膨胀多个这样的项目并将其添加到列表中,即 ScrollView。这显示了我们的 Activity 或 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>
以及填充列表的代码:
我在这里创建了一个虚拟适配器,类似于 ListView,它只枚举我们从 0 到 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();
}
});
}
最后我们得到如下所示的结果:
对于那些正在使用 RecyclerView, an implementation of ItemDecoration 的人,可以在 RecyclerView 上进行设置,这将:
- 偏移项目:覆盖
getItemOffsets()
装饰。在这里可以移动项目,使它们重叠。
- 绘制形状:覆盖
onDraw()
以绘制三角形作为背景。
我需要实现一个带有三角形项目的 ListView,如图所示。添加到 ListView 的视图通常是矩形的。即使在文档中,视图也被描述为 "occupies a rectangular area on the screen and is responsible for drawing and event handling"。
如何将非矩形形状添加到 ListView 并同时确保点击区域限于形状,在本例中为三角形。
谢谢!
一行的两个列表项,并使文本可点击。
- 每行设计图片,每行设计两张图片。
- 对于每个选项,仅使两个项目的文本可点击。
我认为不可能创建实际的三角形视图并将它们添加到列表视图中。在这种情况下你会使用什么布局?
一种方法是使用背景图像来创造幻觉。将由红线分隔的每个段视为列表视图中的一个项目。因此,您必须根据需要为列表视图中的每个项目创建背景图像,并以正确的顺序设置它们。
更新:这就是我在下面评论中对背景图片进行一致切片的意思。
我的解决方案将使用重叠的视图,这些视图被裁剪成交替的三角形,并且只接受其三角形内的触摸事件。
问题是 ListView 并不真正支持重叠的项目视图,因此我的示例只是将所有项目一次加载到 ScrollView 中,如果您有超过 30 个项目,这可能会很糟糕。也许这对于 RecyclerView 是可行的,但我还没有研究过。
我选择扩展 FrameLayout 来实现三角形 View 逻辑,因此您可以将它用作列表项的根 View,并在其中放入任何您想要的东西:
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
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 };
}
}
以 TriangleFrameLayout
为根的项目 XML 布局示例可能如下所示:
<?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>
这里我们有一个固定的高度160dp
,您可以随意更改。重要的是一半高度的负上边距,-80dp
在这种情况下,这会导致项目重叠并且不同的三角形匹配。
现在我们可以膨胀多个这样的项目并将其添加到列表中,即 ScrollView。这显示了我们的 Activity 或 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>
以及填充列表的代码:
我在这里创建了一个虚拟适配器,类似于 ListView,它只枚举我们从 0 到 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();
}
});
}
最后我们得到如下所示的结果:
对于那些正在使用 RecyclerView, an implementation of ItemDecoration 的人,可以在 RecyclerView 上进行设置,这将:
- 偏移项目:覆盖
getItemOffsets()
装饰。在这里可以移动项目,使它们重叠。 - 绘制形状:覆盖
onDraw()
以绘制三角形作为背景。