【问题标题】:Algorithm for alignment guidelines/snapping found in GUI builders在 GUI 构建器中找到的对齐指南/捕捉算法
【发布时间】:2017-01-25 10:59:37
【问题描述】:

我尝试实现与许多 GUI 构建器类似的行为:如果当前拖动的组件几乎与另一个组件在水平或垂直线上,则它们应该与另一个组件对齐。 我目前的方法是遍历所有放置的组件并检查四个边缘中的任何一个是否(几乎)与拖动组件的边缘对齐:

for (v in rootView.relativeLayout.children()) {
    // x
    val left = event.rawX - dXInit
    val right = event.rawX - dXInit + view.width
    val leftEdgeRange = (v.leftEdge() - 50 .. v.leftEdge() + 50)
    val rightEdgeRange = (v.rightEdge() - 50 .. v.rightEdge() + 50)

    when (left) {
        in leftEdgeRange -> x = v.leftEdge()
        in rightEdgeRange -> x = v.rightEdge()
    }
    when (right) {
        in leftEdgeRange -> x = v.leftEdge() - view.width
        in rightEdgeRange -> x = v.rightEdge() - view.width
    }
    // y
    val top = event.rawY - dYInit
    val bottom = event.rawY - dYInit + view.height
    val topEdgeRange = (v.topEdge() - 50 .. v.topEdge() + 50)
    val bottomEdgeRange = (v.bottomEdge() - 50 .. v.bottomEdge() + 50)

    when (top) {
        in topEdgeRange -> y = v.topEdge()
        in bottomEdgeRange -> y = v.bottomEdge()
    }
    when (bottom) {
        in topEdgeRange -> y = v.topEdge() - view.height
        in bottomEdgeRange -> y = v.bottomEdge() - view.height
    }
}

fun View.topEdge() = y
fun View.bottomEdge() = y + height
fun View.leftEdge() = x
fun View.rightEdge() = x + width

但这似乎效率低下,因为这是在 onTouch 中调用的,所以这个循环经常运行。有更好的方法吗?欢迎提供一般或 Java 答案。

【问题讨论】:

    标签: java android user-interface kotlin


    【解决方案1】:

    一种更有效的方法是减少 O(n) 循环,该循环遍历所有视图以 O(log n) 搜索最近的边缘一些排序表示,如TreeSetTreeMap,它们是balanced binary search trees。当然,这需要您为 leftEdgerightEdgetopEdgebottomEdge 中的每一个存储四个单独的排序表示。

    一个简单的例子是(只显示leftEdge,其他类似):

    val viewsByLeftEdge = TreeMap<Int, View>()
    

    要将视图添加到地图中,只需使用:

    viewsByLeftEdge[view.leftEdge()] = view
    

    (注意,如果几个view的左边缘值相同,那么只有最后一个会存储在这个map中)

    之后,您可以找到与给定left 坐标最近的左边缘,而不是遍历所有视图:

    val floorL = viewsByLeftEdge.floorKey(left)
    val ceilingL = viewsByLeftEdge.ceilingKey(left)
    val nearestL = when {
        floorL == null -> ceilingL
        ceilingL == null -> floorL
        ceilingL - left < left - floorL -> ceilingL
        else -> floorL
    }
    
    if (nearestL in left - 50 .. left + 50)
        x = nearestL
    

    这里,.floorKey(x) 返回地图中小于或等于x 的最高边缘坐标,如果没有这样的坐标,则返回null。同样,.ceilingKey(x) 返回地图中大于xnull 的最低坐标。

    这需要 O(log n) 时间,并且比迭代任何大量视图的所有视图要快。如果您不需要获取视图的边缘,请通过将TreeMap&lt;Int, View&gt; 替换为TreeSet&lt;Int&gt; 来简化代码(然后函数是floor(x)ceiling(x)

    您可以将地图放置在您的代码中,用您的Views 填充它们,然后为四个边组合函数以更好地融入您的代码设计。

    【讨论】:

    • 谢谢你的详细回答,我今天试试看:)
    • 好的,我无法理解的是:如果这些边缘之一发生变化该怎么办,我不能简单地删除它并添加新值,因为它可能会删除具有相同值的另一个组件的边缘。如果有任何变化,我是否需要从头开始重建所有地图/场景?
    • @Benjoyo,我认为避免这种情况的最简单解决方法是使用TreeMap&lt;Int, HashSet&lt;View&gt;&gt; 而不是TreeMap&lt;Int, View&gt;,以便您可以检查和更新与边缘值对应的HashSet .然而,这增加了代码复杂性和性能开销。另一种选择是使用例如TreeSet&lt;View&gt;(compareBy&lt;View&gt; { it.leftEdge() }.thenBy { it.id }),其中id 是唯一的。这允许某个边缘值的多个值,但是对这样一个映射的查询需要您构造一个具有所需左边缘值的假View,然后调用例如set.floor(fakeView)
    • 谢谢,40+ 的浏览量运行顺利,我只需要 :)
    • @Benjoyo,很高兴我能帮上忙!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多