Android修行手册-超出父布局进行显示以及超出父布局实现点击

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总
游戏脚本-辅助自动化Android控件全解手册再战Android系列
Scratch编程案例软考全系列Unity3D学习专栏
蓝桥系列ChatGPT和AIGC

👉关于作者

专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎底部卡片私我,交流让学习不再孤单

在这里插入图片描述

👉实践过程

😜超出父布局显示

我们实现一个 LinearLayout 布局,宽高是200,里面嵌套一个 Button ,默认是展示出来的。

<RelativeLayout
    android:layout_width="200mm"
    android:layout_height="200mm"
    android:background="@color/crane_swl_color_3">

    <Button
        android:id="@+id/idBtnText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Excel"
        android:textSize="26mm" />
</RelativeLayout>

在这里插入图片描述

但是我们将 Button 的间距设置超出父布局。默认是不会展示出来的。

<RelativeLayout
    android:layout_width="200mm"
    android:layout_height="200mm"
    android:background="@color/crane_swl_color_3">

    <Button
        android:id="@+id/idBtnText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Excel"
        android:textSize="26mm" />
</RelativeLayout>

在这里插入图片描述
我们需要借住属性:

android:clipChildren="false"
android:clipToPadding="false"

官方对于第一行的解释:
Defines whether a child is limited to draw inside of its bounds or not.
翻译:定义一个子视图是否局限于它的范围内。
所以我们设置为false,让子视图不局限与自己;

官方对于第二行的解释:
Defines whether the ViewGroup will clip its drawing surface so as to exclude the padding area.
翻译:定义ViewGroup是否将剪辑其绘图表面以排除填充区域。

要特别注意

  1. 如果你某个子 View 嵌套了多层,然后超出了父布局,需要所有的父布局都携带 clipChildren 属性。
  2. 这个子 View 的最近父布局需要是 RelativeLayout ,博主摸了摸秀发,并没有去深究为什么其他 ViewGroup 为什么不行。
    如下:
<?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:clipChildren="false"
    android:clipToPadding="false"
    android:orientation="vertical"
    tools:context=".MainActivity"
    tools:ignore="HardcodedText,InOrMmUsage">

    <RelativeLayout
        android:layout_width="200mm"
        android:layout_height="200mm"
        android:background="@color/crane_swl_color_3"
        android:clipChildren="false"
        android:clipToPadding="false">

        <Button
            android:id="@+id/idBtnText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="300mm"
            android:text="Excel"
            android:textSize="26mm" />
    </RelativeLayout>
</LinearLayout>

😜溢出的按钮可以点击

有两种方案

方案一

方案一是在整个Activity窗口捕捉点击事件。

@Override
public boolean onTouchEvent(MotionEvent event) {
    //首先定义一个数组用来接收按钮的坐标xy值
    int[] xy = new int[2];
    //获取按钮的top/left xy值
    //button变量我在onCreat()函数中已经获取了控件,具体按实际情况写
    button.getLocationOnScreen(xy);
	//再定义一个数组用来计算控件的bottom/right xy值
    int[] xy_end = new int[2];
    xy_end[0] = buttom.getWidth() + xy[0];
    xy_end[1] = buttom.getHeight() + xy[1];
	//现在我们已经得到了按钮的左上坐标和右下坐标
	//两个点可以确定一个矩形嘛  event里包含了点击的信息;
	//我们判断点击的坐标是否在按钮坐标内,实际就是判断点击的xy值是否在上述矩形中;
    if (event.getX() >= xy[0] && event.getX() <= xy_end[0]
        && event.getY() >= xy[1] && event.getY() <= xy_end[1]) {
        //如果是,那么就执行里边的代码,在这里我们可以callOnClick()按钮
		//实际体验了一番,发现轻点一下和长按均可以激活按钮;
		//但是,我的按钮拥有animate()事件,所以连续点击会在动画未完成时再次点击按钮,
		//所以我做了个判断,让动画未完成时不再执行点击,机制如我
		//实际中,读者完全不用这两行代码
		//让我看看有哪些读者看都不看直接复制代码--手动滑稽
		//虽说站在巨人肩膀上,但是也要搞懂其原理才不会摔下来。
        if (isMoreShow == false && xy[0] >= button.getHeight())
            return false;
		//我们callOnClick了按钮,也就是模拟点击了按钮;
        button.callOnClick();
        return false;
    }
    return super.onTouchEvent(event);
}

不足之处也很明显,如果页面点击事件要素过多,写入的判断就很多了,毕竟你是整个 Activity 自己处理事件了。

推荐方案二:委托

小应用场景:有时候一个按钮效果很小,就很难触发点击事件,我们通常会增大下这个点击区间范围。
大应用场景:我实现了多个脑图的功能,里面因为方便画线穿插过某个UI,就用到了此类知识。
在这里插入图片描述
其他情况多种多样,相信看这篇文章的你也是因为有这个需求才查找的。

小应用场景的实现很简单:

  1. 直接增大 View 的宽高,然后给View设置内边距 padding ;或者直接嵌套一层给这个父设置点击,但这会增加布局嵌套进而消耗性能。
  2. 利用委托功能直接增大点击的区间范围。
    /**
     * 扩展点击区域的范围
     * @param view       需要扩展的元素,此元素必需要有父级元素
     * @param expendSize 需要扩展的尺寸,当然也可以分别设置增大范围
     */
    public static void expendTouchArea(final View view, final int expendSize) {
        if (view != null) {
            final View parentView = (View) view.getParent();

            parentView.post(new Runnable() {
                @Override
                public void run() {
                    Rect rect = new Rect();
                    view.getHitRect(rect); 
                    rect.left -= expendSize;
                    rect.top -= expendSize;
                    rect.right += expendSize;
                    rect.bottom += expendSize;
                    parentView.setTouchDelegate(new TouchDelegate(rect, view));
                }
            });
        }
    }

事实是,委托就是系统给我们提供的扩大控件点击区域判断范围的代理方式,我们看下View类的源码。

class View{
    /**
     * The delegate to handle touch events that are physically in this view
     * but should be handled by another view.
     */
    private TouchDelegate mTouchDelegate = null;
	public boolean onTouchEvent(MotionEvent event) {
		//...
		if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
	}
    /**
     * Sets the TouchDelegate for this View.
     */
    public void setTouchDelegate(TouchDelegate delegate) {
        mTouchDelegate = delegate;
    }
}

从源码中可以看到如果设置了TouchDelegate,touchEvent会优先交给TouchDelegate来处理。

package android.view;
import android.graphics.Rect;
/**
 * Helper class to handle situations where you want a view to have a larger touch area than its
 * actual view bounds. The view whose touch area is changed is called the delegate view. This
 * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
 * instance that specifies the bounds that should be mapped to the delegate and the delegate
 * view itself.
 * The ancestor should then forward all of its touch events received in its
 * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
 */
public class TouchDelegate {
    private View mDelegateView;
    private Rect mBounds;
    private boolean mDelegateTargeted;
    public TouchDelegate(Rect bounds, View delegateView) {
        mBounds = bounds;
        mDelegateView = delegateView;
    }
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        boolean sendToDelegate = false;
        boolean hit = true;
        boolean handled = false;
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mDelegateTargeted = mBounds.contains(x, y);
                sendToDelegate = mDelegateTargeted;
                break;
                //...
        }
        if (sendToDelegate) {
            final View delegateView = mDelegateView;
            if (hit) {
                // Offset event coordinates to be inside the target view
                event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mSlop;
                event.setLocation(-(slop * 2), -(slop * 2));
            }
            handled = delegateView.dispatchTouchEvent(event);
        }
        return handled;
    }
}

从源码中 可以看到,创建TouchDelegate 需要传入一个Rect(left,top,right,bottom) 和delegateView, onTouchEvent触发时,会通过这个Rect来判断点击事件是否落在区域内,如果是 则转发给代理view来处理该事件。

复杂场景实现-重点

子 View 超出父布局显示,然后触发点击事件,同样利用的委托功能,但是因为要处理 Touch 需要自定义一下。

<?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:clipChildren="false"
    android:clipToPadding="false"
    android:id="@+id/rootLay"
    android:orientation="vertical"
    tools:context=".MainActivity"
    tools:ignore="HardcodedText,InOrMmUsage">
    
    <cn.akitaka.test.TestOverClick
        android:id="@+id/testLay"
        android:layout_width="200mm"
        android:layout_height="200mm"
        android:background="@color/crane_swl_color_3"
        android:clipChildren="false"
        android:clickable="true"
        android:clipToPadding="false">
<!--特别留意,因为是自定义的RelativeLayout,Touch时间默认只有个down,需要设置可点击才能回调所有的事件-->
        <Button
            android:id="@+id/idBtnTest"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="300mm"
            android:text="Excel"
            android:textSize="26mm" />
    </cn.akitaka.test.TestOverClick>
</LinearLayout>
/**
 * @author akitaka 2023/11/22 960576866@qq.com
 * @describe TestOverClick
 */
public class TestOverClick extends RelativeLayout {

    public TestOverClick(Context context) {
        super(context);
    }

    public TestOverClick(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestOverClick(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TestOverClick(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initClickRect() {
        View rootParent = ((View) getParent());// 获取父视图
        rootParent.post(() -> {// 将当前代码放在消息队列中异步执行
            Rect rect = new Rect();// 创建一个矩形对象
            // 获取当前视图的点击区域  如果太早执行本函数,会获取rect失败,因为此时UI界面尚未开始绘制,无法获得正确的坐标
            getHitRect(rect);
            rect.left -= 0;
            rect.top -= 0;
            //布局中控件是距离左300像素 控件本身是200 他俩的中间间距为100 加上按钮的本身宽度
            rect.right += AutoSizeUtils.mm2px(getContext(), 100) + btn.getWidth();
            rect.bottom += 0;

            rootParent.setTouchDelegate(new TouchDelegate(rect, this));  // 设置根视图的触摸委托为当前视图
        });
    }

    private Button btn;//外部的按钮对象设置

    public void setBtn(Button btn) {
        this.btn = btn;
        initClickRect();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG", "事件类型: " + event.getAction());
        int x = (int) event.getX();
        int y = (int) event.getY();
//        if () {  TODO  重点注意
//            //这个if判断是你点击的x、y坐标是否在按钮的范围内,不在的话直接进行return不处理即可
//            //具体的区间判断范围,就需要自己的项目具体调整了。
//            return true;
//        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("TAG", "按下事件: " + btn);
                btn.setBackgroundResource(R.color.purple_200);
                break;
            case MotionEvent.ACTION_UP:
                btn.performClick();
                Log.e("TAG", "抬起事件: " + btn);
                HandlerUtils.INSTANCE.postRunnable(() -> {
                    btn.setBackgroundResource(R.color.purple_700);
                }, 30);//30毫秒延迟
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}
public class MainActivity extends FragmentActivity {
		@Override
		protected void onCreate(Bundle savedInstanceState) {
				super.onCreate(savedInstanceState);
				setContentView(R.layout.activity_main);
				TestOverClick testLay = findViewById(R.id.testLay);
				Button idBtnTest = findViewById(R.id.idBtnTest);
				idBtnTest.setOnClickListener(v -> Log.e("TAG", "点击了内容: "));
				testLay.setBtn(idBtnTest);
		}
}

上面的注释简直是保姆级的了。

  1. 自定义 TestOverClick 嵌套了个子 Button 控件,设置android:clickable="true"可点击,设置属性android:clipChildren="false"android:clipToPadding="false"实现超出区域可见。
  2. 自定义 TestOverClick 有个方法 initClickRect 是用来设置点击响应区域的,咱们向右侧进行了扩大,红色为默认响应区域,经过计算:布局中控件是距离左300像素 控件本身是200 他俩的中间间距为100 加上按钮的本身宽度。右侧增加了绿框范围的响应区域。
    在这里插入图片描述
  3. 接着我们在 onTouchEvent 函数中做两个处理:处理一是判断下点击的区间,通过计算允许在按钮范围内处理,否则的话直接消耗事件,这样就假装模拟出了只响应按钮了。处理二是在事件中抬起的时候回调下按钮的模拟点击事件,就会进入业务逻辑。注意我们真正点击的其实是父控件,只不过模拟点击了按钮。
  4. 默认模拟点击是没有点击效果的,所以我们在 onTouchEvent 中 down 和 up 的时候自己更改下按钮背景状态即可完美实现点击UI变化。
  5. activity 中直接使用即可,我们内部需要用到按钮,记得要传递进去按钮对象。

题外

一个Parent只能设置一个View的TouchDelegate,设置多个时只有最后设置的生效。
如果想恢复 View 的触摸范围:

/**
 * 还原View的触摸和点击响应范围,最小不小于View自身范围
 */
public static void restoreViewTouchDelegate(final View view) {
	((View) view.getParent()).post(new Runnable() {
		@Override
		public void run() {
			Rect bounds = new Rect();
			bounds.setEmpty();
			TouchDelegate touchDelegate = new TouchDelegate(bounds, view);
			if (View.class.isInstance(view.getParent())) {
				((View) view.getParent()).setTouchDelegate(touchDelegate);
			}
		}
	});
}

还没懂?下方卡片联系我,手把手教你。

👉其他

📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

温馨提示点击下方卡片获取更多意想不到的资源。
空名先生

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/182561.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

postgresql数据库中update使用的坑

简介 在数据库中进行增删改查比较常见&#xff0c;经常会用到update的使用。但是在近期发现update在oracle和postgresql使用却有一些隐形区别&#xff0c;oracle 在执行update语句的时候set 后面必须跟着1对1的数据关联而postgresql数据库却可以一对多&#xff0c;这就导致数据…

GaussDB技术解读系列:数据实例的连接

GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库&#xff0c;该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持优异的分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000扩展能力&#xff0c;PB级海量存储等企业级数据库特性。…

【实战精选】掌握图像风格迁移:构建基于生成对抗网络的系统

1.研究背景与意义 随着计算机技术的不断发展&#xff0c;图像处理和计算机视觉领域取得了长足的进步。图像风格迁移是其中一个备受关注的研究方向&#xff0c;它可以将一幅图像的风格特征应用到另一幅图像上&#xff0c;从而创造出新的图像。这项技术具有广泛的应用前景&#…

RK3399平台开发系列讲解(内核入门篇)ConfigFS 的核心数据结构

🚀返回专栏总目录 文章目录 一、关键数据结构二、config_item 的结构体三、属性和方法沉淀、分享、成长,让自己和他人都能有所收获!😄 📢虚拟文件系统 ConfigFS 是一个特殊的文件系统,旨在提供一种动态配置 Linux 内核和设备的机制。 一、关键数据结构 ConfigFS 的核…

解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持)

目录 Reder版本2019.x Reder版本2021.1.5x Reder版本2019.x 解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持) Reder版本2021.1.5x 解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持)…

【精选】​​通道热点加持的LW-ResNet:小麦病害智能诊断与防治系统

1.研究背景与意义 小麦是世界上最重要的粮食作物之一&#xff0c;但由于病害的侵袭&#xff0c;小麦产量和质量受到了严重的威胁。因此&#xff0c;开发一种高效准确的小麦病害识别分类防治系统对于保障粮食安全和农业可持续发展具有重要意义。 传统的小麦病害识别分类方法主…

6.1.webrc媒体协商

那今天呢&#xff1f;我们来看一下y8 rtc的媒体协商&#xff0c;那实际上在我们之前的课程中呢&#xff1f;我已经向你介绍过y8 rtc的媒体协商了。只不过呢&#xff0c;角度是不一样的&#xff0c;在之前介绍外边tc媒体协商的时候呢&#xff0c;我们是从应用的角度来看。那web …

C++的new / delete 与 C语言的malloc/realloc/calloc / free 的讲解

在C语言中我们通常会使用malloc/realloc/calloc来动态开辟的空间&#xff0c;malloc是只会开辟你提供的空间大小&#xff0c;并不会初始化内容&#xff1b;calloc不但会开辟空间&#xff0c;还会初始化&#xff1b;realloc是专门来扩容的&#xff0c;当你第一次开辟的空间不够用…

【MATLAB】全网入门快、免费获取、持续更新的科研绘图教程系列1

1 【MATLAB】科研绘图第一期点线图 %% Made by Lwcah %% 公众号&#xff1a;Lwcah %% 知乎、B站、小红书、抖音同名账号:Lwcah&#xff0c;感谢关注~ %% 更多MATLABSCI绘图教程敬请观看~%% 清除变量 clc; clear all; close all;%% 一幅图的时候figureWidth 8.5;figureHeight …

鸿蒙 ark ui 轮播图实现教程

前言&#xff1a; 各位同学有段时间没有见面 因为一直很忙所以就没有去更新博客。最近有在学习这个鸿蒙的ark ui开发 因为鸿蒙不是发布了一个鸿蒙next的测试版本 明年会启动纯血鸿蒙应用 所以我就想提前给大家写一些博客文章 效果图 具体实现 我们在鸿蒙的ark ui 里面列表使…

SPASS-信度分析

信度分析概述 效度 效度指的是量表是否真正反映了我们希望测量的东西。一般来说&#xff0c;有4种类型的效度&#xff1a;内容效度、标准效度、结构效度和区分效度。内容效度是一种基于概念的评价指标&#xff0c;其他三种效度是基于经验的评价指标。如果一个量表实际上是有效…

使用 NVProf 检测 CUDA kernel 的 bank conflict

使用 NVProf 检测 CUDA kernel 的 bank conflict NVProf 指令 使用 NVProf 可以对 bank conflict 进行检测: nvprof --events shared_ld_bank_conflict,shared_st_bank_conflict <app> [args...]其中: --events 选项指定的 shared_ld_bank_conflict,shared_st_bank_c…

PTA-成绩转换

本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则&#xff1a; 大于等于90分为A&#xff1b;小于90且大于等于80为B&#xff1b;小于80且大于等于70为C&#xff1b;小于70且大于等于60为D&#xff1b;小于60为E。 输入格式: 输入在一行中给出一个整数的百分制成…

WordPress网站如何修复数千个帖子的SEO错误

在本教程中&#xff0c;我们将向您展示如何解决您经常犯的SEO错误。 最好的是您不必花费太多时间&#xff0c;因为您不需要打开并编辑每个帖子。 相反&#xff0c;我们将向您展示如何使用 WordPress 内的电子表格来修复 WordPress 帖子的 SEO。 在这里&#xff0c;我们为您提…

Spring-jdbcTemplate-配置数据库连接池,配置文件方式beans.xml

1、jdbc.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql:///studb jdbc.userroot jdbc.pwd123456 2、beans.xml <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans&…

计算机中了halo勒索病毒怎么清除,halo勒索病毒解密数据恢复

科技的进步加快了企业发展的步伐&#xff0c;网络技术的不断应用为企业的生产运营提供了极大帮助&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了halo勒索病毒攻击&#…

虚拟机解决Linux中Uos和Deepin登录密码忘记的问题 标题Linux Uos Deepin

Uos是切换网络模式解决的(之前有绑定过用户) 因为之前用的是桥接模式登录的时候一直无法联网,改为Nat模式后可以和电脑共用一个网络ip,可以重置密码了,以此解决 ps: 特别说明rw single init/bin/bash 方法和systemd.debug-shell1方法已经失效,不要再做无谓的尝试了Deepin23社区…

怎样用AIDL Service 传递复杂数据

大家都知道在Android中通过AIDL可以跨进程调用Service中的数据&#xff0c;网上也有很多实例&#xff0c;但是大部分实例都是关于基本数据类型的远程调用&#xff0c;很少讲到复杂数据的调用&#xff0c;今天我用一个例子来演示一下怎样用AIDL Service 传递复杂数据。 我们分2…

HTTP客户端警告:Going to buffer response body of large or unknown size

HTTP客户端警告&#xff1a;Going to buffer response body of large or unknown size 点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 目录 HTTP客…

筑牢思想防线——建行驻江门市分行纪检组举办2023年清廉合规大讲堂

为推动廉洁教育打通“最后一公里”&#xff0c;近日&#xff0c;建行驻江门市分行纪检组举办江门市分行2023年清廉合规大讲堂。 本次大讲堂检察官结合一线办案经历&#xff0c;从防范化解金融风险、预防金融从业人员犯罪等方面对全辖员工进行了深入浅出地的讲解&#xff0c;引导…