Android根据座标找到对应的View

本文探讨了如何在Android中根据坐标找到对应的顶层View。当用户在屏幕上点击或滑动时,系统通过以DecorView为根的多叉树结构进行事件分发。事件从最后一个子节点开始传递,由ViewGroup的dispatchTouchEvent函数实现。在LinearLayout中可从前向后遍历子View,但在FrameLayout或RelativeLayout中需从后向前,以处理覆盖情况。为找到最上层的View,需要进行深度优先搜索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在界面上点击按钮时,想想安卓是如何找到能响应事件的顶层View的?
如果给你坐标x、y, 你能找到对应的顶层View么?

首先安卓布局根节点是DecorView,并呈现为多叉树结构; 每个顶层View都是一个叶节点;

需求:手指在界面上滑动时显示对应的顶层View。
在这里插入图片描述

Activity显示在屏幕上时, 以DecorView为根节点并呈现多叉树数据结构;
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".MainActivity"
  >
    <TextView
      android:id="@+id/tv_id"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Hello World!"
      />
    <Button
      android:id="@+id/btn_ok"
      android:layout_width="100dp"
      android:layout_height="50dp"
      android:layout_marginTop="10dp"
      android:text="按钮"
      />
    <FrameLayout
      android:id="@+id/fl_container"
      android:layout_width="match_parent"
      android:layout_height="100dp"
      android:background="#00ff00"
      >
        <TextView
          android:layout_width="100dp"
          android:layout_height="50dp"
          android:layout_gravity="center"
          android:gravity="center"
          android:text="测试"
          android:background="#ff0000"
          />
    </FrameLayout>
    <TextView
      android:id="@+id/tv_title"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginLeft="10dp"
      android:layout_marginTop="100dp"
      android:text="控件信息"
      />
    <TextView
      android:id="@+id/tv_info"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="20dp"
      android:layout_marginLeft="10dp"
      />
</LinearLayout>

背景知识:
安卓事件分发时从最后一个子节点开始传递事件, 原因是什么呢?
ViewGroup.java的dispatchTouchEvent函数在遍历子View时, 是从最后一个子View开始分发事件的, 详见第2629行;
在这里插入图片描述
常用安卓ViewGroup为LinearLayout、FrameLayout和RelativeLayout, 如果当前ViewGroup是LinearLayout从前向后遍历也行; 但如果是FrameLayout或RelativeLayout就必须从后向前遍历,因为后边的子View可以覆盖前面的子View;

对于Android界面可以从DecorView开始查找, 如果坐标x,y在控件矩形范围内则继续向下递归,为了找到叶节点(即最上面的View)需要深度优先;

public class MainActivity extends AppCompatActivity {
    private TextView mTvInfo;

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

        mTvInfo = findViewById(R.id.tv_info);
    }

    @Override public boolean onTouchEvent(MotionEvent event) {
        showWidgetInfo(event.getX(), event.getY());
        return super.onTouchEvent(event);
    }

    private void showWidgetInfo(float x, float y) {
        View view = getViewByPosition(getWindow().getDecorView(), (int)x, (int)y);
        if (view != null) {
            mTvInfo.setText(view.toString());
        } else {
            mTvInfo.setText("没找到匹配的View");
        }
    }

    private View getViewByPosition(View view, int x,  int y) {
        if (view == null) {
            return null;
        }

        int[] location = new int[2];
        view.getLocationInWindow(location);

        int left = location[0];
        int top  = location[1];
        int right = left + view.getWidth();
        int bottom = top + view.getHeight();

        if (view instanceof ViewGroup) { //当前是ViewGroup容器
            int childCount = ((ViewGroup)view).getChildCount();
            //深度优先, 从最后一个子节点开始遍历,如果找到则返回。 先递归判断子View
            if (childCount > 0) {
                for (int i = childCount - 1; i >= 0; i--) {
                    View topView = getViewByPosition(((ViewGroup)view).getChildAt(i), x, y);
                    if (topView != null) {
                        return topView;
                    }
                }
            }

            //子View都没找到匹配的, 再判断自己
            if (left < x && top < y && right > x && bottom > y) {
                return view;   //当前ViewGroup就是顶层View
            } else {
                return null; //没找到匹配的
            }
        } else { //当前是View
            if (left < x && top < y && right > x && bottom > y) {
                return view;   //当前ViewGroup就是顶层View
            } else {
                return null; //没找到匹配的
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值