我需要实现一个带有三角形项目的ListView,如图所示.添加到ListView的视图通常是矩形的.即使在文档中,视图也被描述为"占据屏幕上的矩形区域并负责绘图和事件处理".
如何将非矩形形状添加到ListView,同时确保单击区域仅限于形状,在本例中为三角形.
谢谢!
我的解决方案将使用重叠视图,这些视图被裁剪为交替三角形,并且仅接受其三角形内的触摸事件.
问题是ListView并不真正支持重叠的项目视图,因此我的示例只是将所有项目一次加载到ScrollView中,如果您有超过30个项目,这可能会很糟糕.也许这对RecyclerView来说是可行的,但我还没有考虑过.
我已选择扩展FrameLayout以实现三角形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 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 }; } }
具有TriangleFrameLayout
root用户的示例项XML布局可能如下所示:
在这里,我们有一个固定的高度160dp
,你可以改变你想要的任何东西.重要的是在一半高度的负上边距,-80dp
在这种情况下,导致项目重叠和不同的三角形匹配.
现在我们可以膨胀多个这样的项目并将其添加到列表中,即ScrollView.这显示了我们的Activity或Framgent的示例布局:
以及填充列表的代码:
在这里,我创建了一个虚拟适配器,类似于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(); } }); }
最后,我们得到一个如下所示的结果:
一行列表中的两项,并使文本可单击.
为每个行设计图像,为一个项目设计两个图像.
对于每个选项,只使两个项目的文本可单击.