【问题标题】:ListView with triangular shaped items带有三角形项目的 ListView
【发布时间】:2016-01-02 11:47:03
【问题描述】:

我需要实现一个带有三角形项目的 ListView,如图所示。添加到 ListView 的视图通常是矩形的。即使在文档中,视图也被描述为“占据屏幕上的一个矩形区域并负责绘图和事件处理”。

如何向 ListView 添加非矩形形状,同时确保点击区域仅限于形状,在本例中为三角形。

谢谢!

【问题讨论】:

  • 这可以通过在创建三角形错觉的序列中设置背景图像来完成。
  • 你能告诉我你在哪里找到这个布局图吗?
  • @piotrek1543,我的 UI/UX 设计师给我的布局。

标签: android listview android-listview android-view


【解决方案1】:

我的解决方案将使用被裁剪为交替三角形的重叠视图,并且只接受其三角形内的触摸事件。

问题在于 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 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 作为根的 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();
            }
        });
    }

最后我们得到的结果如下所示:

【讨论】:

  • 非常感谢@Floern。这种方法看起来很可靠。只关心项目数量的限制。无论如何,会试试这个。
  • 我想这是我们可以根据需要使用布局的正确方法 +1 为你 @Floern
  • @Floern。嘿,这种方法奏效了。使用这种方法,我可以在我的旧 Nexus 4 上毫无延迟地获得大约 40 个项目。将尝试为 RecyclerView 修改它并检查是否可以删除项目计数限制。非常感谢!
【解决方案2】:

一行的两个列表项并使文本可点击。

  • 为每行设计图片,为一件商品设计两张图片。
  • 对于每个选项,仅使两个项目的文本都可点击。

【讨论】:

  • 谢谢@Umar,但只保留文本可点击不是一个选项,因为我需要整个三角形都可以点击。
  • @NarayanAcharya 我认为应该可以为单行定义可点击区域。看看here
【解决方案3】:

我认为不可能创建实际的三角形视图并将它们添加到列表视图中。在这种情况下你会使用什么布局?

一种方法是使用背景图片来营造一种错觉。将由红线分隔的每个段视为列表视图中的一个项目。因此,您必须根据需要为列表视图中的每个项目创建背景图像,并以正确的顺序设置它们

更新:这就是我在下面的 cmets 中对背景图像进行一致切片的意思。

【讨论】:

  • 在我看来,这只是作者创建的一个背景,但是是的,它是三角形的混合。我和你有类似的想法;-)
  • @NarayanAcharya:您还可以在单​​击时通过识别单击列表视图项的位置然后对其做出反应来创建错觉,可能无法提供像素完美点检测,但可以通过定义范围来管理并根据在每个列表视图项中单击的范围对单击做出反应。
  • 这是一次性的右切背景以使它们在范围方面保持一致,然后编写区域检测方法来确定单击了哪个范围 在列表项视图中。
  • @piotrek1543 是的,纳拉扬想要的并不简单。所以让它发挥作用也很困难。 :-) ...没有正确的已知或现成的方法,因此这个问题更像是寻找最佳方法的头脑风暴。我喜欢这样的问题。
  • 添加了一个示例来演示如何在每个列表视图项中仅使用两个片段进行一致的切片。注意:文本需要成为图像的一部分,并且如果您想让它们保持垂直居中,还需要在切片上拆分。还没有添加。
【解决方案4】:

对于现在使用RecyclerView 的用户,可以在RecyclerView 上设置ItemDecoration 的实现,这将:

  1. 偏移项目:覆盖装饰的getItemOffsets()。在这里可以移动项目以使它们重叠。
  2. 绘制形状:覆盖 onDraw() 以绘制三角形作为背景。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-06
    • 2014-10-12
    • 2014-05-20
    • 1970-01-01
    相关资源
    最近更新 更多