Coordinatelayout 与有向无环图的拓扑排序

Coordinatelayout 如何处理子View之间的依赖关系

通过继承 public abstract static class Behavior 并且override 以下几个方法

       public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency);
        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency);
        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency);

我们可以实现,在Coordinatelayout 中一个View 可以 依赖于另一个View 。
这种依赖关系,体现在:

  • CoordinateLayout 的 子View 的布局关联
  • 子View 之间的位移联动上

例如常见的布局 ViewPager 通过 ScrollingViewBehavior 让自己处于 AppBarlayout 下面
在这里插入图片描述

当被依赖的View 的信息发生变化(位置,大小)会回调 onDependentViewChanged方法 。

依赖关系的建立会面临一下问题:

  • 如果 A 依赖 B ,B 又依赖A ,当 A 产生了一个滑动,B 对滑动做出响应并且自己也做了位移操作,并且将位移事件又传递给了A 。形成了死循环,所以如何检测循环依赖?
  • A依赖B ,B 又依赖 C 。这种依赖关系使用什么样的数据结构去保存?
  • 当Coordinatelayout 在初次布局子View 时,按照什么样的顺序去测量和Layout ?

以上的问题也就是图的经典问题:有向图是否有环,邻接表,拓扑排序。
所以,我们的Coordinatelayout 使用了图这种数据结构来依赖关系。并且是有向图。 而检测循环依赖,就是检测有向图有没有环。

有向图的数据结构

图的常见存储方式有两种,一种是使用邻接矩阵(Adjacency Matrix)
“以下介绍,引用自数据结构与算法之美”

邻接矩阵的底层依赖一个二维数组。对于无向图来说,如果顶点 i 与顶点 j 之间有边,我们就将 A[i][j]和 A[j][i]标记为 1;对于有向图来说,如果顶点 i 到顶点 j 之间,有一条箭头从顶点 i 指向顶点 j 的边,那我们就将 A[i][j]标记为 1。同理,如果有一条箭头从顶点 j 指向顶点 i 的边,我们就将 A[j][i]标记为 1
在这里插入图片描述

用邻接矩阵来表示一个图,虽然简单、直观,但是比较浪费存储空间。当我们存储的是稀疏图(Sparse Matrix),也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了。
Coordinatelayout 的边不是很多,顶点也不是很多。所以使用了另一种数据结构:邻接表(Adjacency List)

邻接表(Adjacency List):
邻接表是一个二维容器,第一维描述某个点,第二维描述这个点所对应的边集们。
可以通过多种的存储形式来实现邻接表
在这里插入图片描述

Coordinatelayout 使用有向无环图DirectedAcyclicGraph

功能细节:

  • DirectedAcyclicGraph 通过临接表来 描述图
  • Pools.Pool<ArrayList> mListPool 使用享元模式减少对象的创建
  • 使用DFS 进行拓扑排序,以及判断是否有环。
public final class DirectedAcyclicGraph<T> {
    private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
    private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();

    private final ArrayList<T> mSortResult = new ArrayList<>();
    private final HashSet<T> mSortTmpMarked = new HashSet<>();

    /**
     * Add a node to the graph.
     *
     * <p>If the node already exists in the graph then this method is a no-op.</p>
     *
     * @param node the node to add
     */
    public void addNode(@NonNull T node) {
        if (!mGraph.containsKey(node)) {
            mGraph.put(node, null);
        }
    }

    /**
     * Returns true if the node is already present in the graph, false otherwise.
     */
    public boolean contains(@NonNull T node) {
        return mGraph.containsKey(node);
    }

    /**
     * Add an edge to the graph.
     *
     * <p>Both the given nodes should already have been added to the graph through
     * {@link #addNode(Object)}.</p>
     *
     * @param node the parent node
     * @param incomingEdge the node which has is an incoming edge to {@code node}
     */
    public void addEdge(@NonNull T node, @NonNull T incomingEdge) {
        if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
            throw new IllegalArgumentException("All nodes must be present in the graph before"
                    + " being added as an edge");
        }

        ArrayList<T> edges = mGraph.get(node);
        if (edges == null) {
            // If edges is null, we should try and get one from the pool and add it to the graph
            edges = getEmptyList();
            mGraph.put(node, edges);
        }
        // Finally add the edge to the list
        edges.add(incomingEdge);
    }

    /**
     * Get any incoming edges from the given node.
     *
     * @return a list containing any incoming edges, or null if there are none.
     */
    @Nullable
    public List getIncomingEdges(@NonNull T node) {
        return mGraph.get(node);
    }

    /**
     * Get any outgoing edges for the given node (i.e. nodes which have an incoming edge
     * from the given node).
     *
     * @return a list containing any outgoing edges, or null if there are none.
     */
    @Nullable
    public List<T> getOutgoingEdges(@NonNull T node) {
        ArrayList<T> result = null;
        for (int i = 0, size = mGraph.size(); i < size; i++) {
            ArrayList<T> edges = mGraph.valueAt(i);
            if (edges != null && edges.contains(node)) {
                if (result == null) {
                    result = new ArrayList<>();
                }
                result.add(mGraph.keyAt(i));
            }
        }
        return result;
    }

    /**
     * Checks whether we have any outgoing edges for the given node (i.e. nodes which have
     * an incoming edge from the given node).
     *
     * @return <code>true</code> if the node has any outgoing edges, <code>false</code>
     * otherwise.
     */
    public boolean hasOutgoingEdges(@NonNull T node) {
        for (int i = 0, size = mGraph.size(); i < size; i++) {
            ArrayList<T> edges = mGraph.valueAt(i);
            if (edges != null && edges.contains(node)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Clears the internal graph, and releases resources to pools.
     */
    public void clear() {
        for (int i = 0, size = mGraph.size(); i < size; i++) {
            ArrayList<T> edges = mGraph.valueAt(i);
            if (edges != null) {
                poolList(edges);
            }
        }
        mGraph.clear();
    }

    /**
     * Returns a topologically sorted list of the nodes in this graph. This uses the DFS algorithm
     * as described by Cormen et al. (2001). If this graph contains cyclic dependencies then this
     * method will throw a {@link RuntimeException}.
     *
     * <p>The resulting list will be ordered such that index 0 will contain the node at the bottom
     * of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
     */
    @NonNull
    public ArrayList<T> getSortedList() {
        mSortResult.clear();
        mSortTmpMarked.clear();

        // Start a DFS from each node in the graph
        for (int i = 0, size = mGraph.size(); i < size; i++) {
            dfs(mGraph.keyAt(i), mSortResult, mSortTmpMarked);
        }

        return mSortResult;
    }

    private void dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked) {
        if (result.contains(node)) {
            // We've already seen and added the node to the result list, skip...
            return;
        }
        if (tmpMarked.contains(node)) {
            throw new RuntimeException("This graph contains cyclic dependencies");
        }
        // Temporarily mark the node
        tmpMarked.add(node);
        // Recursively dfs all of the node's edges
        final ArrayList<T> edges = mGraph.get(node);
        if (edges != null) {
            for (int i = 0, size = edges.size(); i < size; i++) {
                dfs(edges.get(i), result, tmpMarked);
            }
        }
        // Unmark the node from the temporary list
        tmpMarked.remove(node);
        // Finally add it to the result list
        result.add(node);
    }

    /**
     * Returns the size of the graph
     */
    int size() {
        return mGraph.size();
    }

    @NonNull
    private ArrayList<T> getEmptyList() {
        ArrayList<T> list = mListPool.acquire();
        if (list == null) {
            list = new ArrayList<>();
        }
        return list;
    }

    private void poolList(@NonNull ArrayList<T> list) {
        list.clear();
        mListPool.release(list);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值