Android遍历所有控件的递归和非递归实现

题目描述

给出布局的根节点,要求不使用递归的方式将所有类型为Button的控件背景设置为红色。

分析

对于Android中的布局来说,有两种类型的节点,一种是ViewGroup布局,另外一种是View控件,按照类似树形结构来组织(注意,不是二叉树)。
对于控件的遍历,可以转化为对树的遍历。对树的遍历有递归方式和非递归的方式,非递归方式又可以分为深度优先遍历和广度优先遍历。

实现

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:background="#abcdef"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#156ec7"
        android:orientation="horizontal"
        android:padding="10dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:text="文本1"
            android:textColor="#ffffff" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:text="文本2"
            android:textColor="#ffffff" />


    </LinearLayout>


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮1" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#5d9726"
        android:padding="10dp">

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮2" />

        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/btn"
            android:padding="5dp"
            android:text="文本3"
            android:textColor="#ffffff"

            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/btn"
            android:layout_toRightOf="@id/tv"
            android:padding="5dp"
            android:text="文本4"
            android:textColor="#ffffff" />

    </RelativeLayout>

</LinearLayout>

界面效果和对应的树结构如下:

其中有颜色的节点类型为viewGroup

Android遍历所有控件的递归和非递归实现 Android遍历所有控件的递归和非递归实现
布局 抽象树结构

具体算法实现

以下方式实现了三种方式的遍历结果,读者可以参考。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewGroup root = (ViewGroup) findViewById(R.id.rootView);
        travelTree3(root);
    }
    //递归遍历树

    private void travelTree1(View root) {
        if (root instanceof ViewGroup) {
            int childCount = ((ViewGroup) root).getChildCount();
            for (int i = 0; i < childCount; i++) {
                travelTree1(((ViewGroup) root).getChildAt(i));
            }
        } else if (root instanceof View) {
            Log.i("visitView", root.toString());
            if (root instanceof Button)
                root.setBackgroundColor(Color.parseColor("#ff0000"));
        }
    }

    //非递归广度遍历,利用队列数据结构
    private void travelTree2(View root) {
        ArrayDeque queue = new ArrayDeque();
        queue.addLast(root);
        while (!queue.isEmpty()) {
            //取得队头
            View front = (View) queue.getFirst();
            //如果为viewGroup则使子节点入队列
            if (front instanceof ViewGroup) {
                int childCount = ((ViewGroup) front).getChildCount();
                for (int i = 0; i < childCount; i++) {
                    queue.addLast(((ViewGroup) front).getChildAt(i));
                }

            }
            //如果队头为View类型,输出
            else if (front instanceof View)
                Log.i("visitView", front.toString());
            //队头出队
            queue.pollFirst();
        }
    }

    //非递归深度遍历,利用栈数据结构

    private void travelTree3(View root) {

        ArrayDeque stack = new ArrayDeque();
        stack.addLast(root);
        while (!stack.isEmpty()) {
            //取得栈顶
            View top = (View) stack.getLast();
            //出栈
            stack.pollLast();
            //如果为viewGroup则使子节点入栈
            if (top instanceof ViewGroup) {
                int childCount = ((ViewGroup) top).getChildCount();
                for (int i = childCount - 1; i >= 0; i--) {
                    stack.addLast(((ViewGroup) top).getChildAt(i));
                }
            }
            //如果栈顶为View类型,输出
            else if (top instanceof View)
                Log.i("visitView", top.toString());

        }
    }


}