Android EditText setTranslationY导致输入法覆盖问题

news2024/10/10 12:19:42

平台

RK3288 + Android 8.1
显示: 1920x1080 @ 160 dpi

概述

碰到一个问题: 弹出的输入法会覆盖文本输入框。
原因:输入框使用了setTranslationY() 位置偏移后, 输入法无法正确获取焦点的位置。

分析

先上图: 初始布局
在这里插入图片描述
调用etTranslationY(700);
在这里插入图片描述
弹出输入法
在这里插入图片描述
最后一张图中, 输入框大概在红框的位置, 也是本文所描述的问题: 输入法遮挡了输入框控件


  • 布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout android:id="@+id/llEdit"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv"
            android:textSize="28sp"
            android:gravity="center"
            android:text="--------------FOOTER---------------------"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <EditText android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="36sp"
            />
        <TextView
            android:id="@+id/tv2"
            android:textSize="28sp"
            android:gravity="center"
            android:text="--------------HEADER---------------------"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>


    <Button android:id="@+id/btTy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="TranslationY"/>
</RelativeLayout>
  • java
package com.android.apitester.test;


import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

import com.android.apitester.R;

public class EditTextTranslationTest extends Activity {
	EditText et;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test_edittext_translation);

		et = (EditText) findViewById(R.id.et);
		findViewById(R.id.btTy).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				et.setTranslationY(et.getTranslationY() != 0 ? 0 : 700);
			}
		});
	}
}


稍微改下代码,把输入框放到界面底部
在这里插入图片描述
在这里插入图片描述
输入法正常弹出,并把整体UI往上顶。

后续做了一些数据, getTranslationY不同的大小以作比对

位移大小展示效果备注
-300被覆盖-
-70被覆盖-
-69第一次后正常第一次被覆盖
-50第一次后正常第一次被覆盖
>0被覆盖-

70 是控件的高度!

输入法是怎么把布局顶上去的? 答案在ViewRootImpl中。

frameworks/base/core/java/android/view/ViewRootImpl.java

    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        String innerPrefix = prefix + "  ";
        //..... 省略 .....
		writer.print(innerPrefix); 
		writer.print("getCurrY=");writer.print(mScroller != null ? mScroller.getCurrY():0);
		writer.print("mScrollY=");writer.print(mScrollY);
		writer.print("mCurScrollY=");writer.print(mCurScrollY);
	}

dumpsys activity name

//未打开输入法
getCurrY=0,mScrollY=0,mCurScrollY=0

//打开输入法
getCurrY=372,mScrollY=372,mCurScrollY=372

准确地说,是上去的

ViewRootImpl doTraversal performTraversals draw scrollToRectOrFocus ViewRootImpl
    boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
        final Rect ci = getWindowInsets(false).getSystemWindowInsetsAsRect();
        final Rect vi = mAttachInfo.mVisibleInsets;
        int scrollY = 0;
        boolean handled = false;

        if (vi.left > ci.left || vi.top > ci.top
                || vi.right > ci.right || vi.bottom > ci.bottom) {
            // We'll assume that we aren't going to change the scroll
            // offset, since we want to avoid that unless it is actually
            // going to make the focus visible...  otherwise we scroll
            // all over the place.
            scrollY = mScrollY;
            // We can be called for two different situations: during a draw,
            // to update the scroll position if the focus has changed (in which
            // case 'rectangle' is null), or in response to a
            // requestChildRectangleOnScreen() call (in which case 'rectangle'
            // is non-null and we just want to scroll to whatever that
            // rectangle is).
            final View focus = mView.findFocus();
            if (focus == null) {
                return false;
            }
            View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
            if (focus != lastScrolledFocus) {
                // If the focus has changed, then ignore any requests to scroll
                // to a rectangle; first we want to make sure the entire focus
                // view is visible.
                rectangle = null;
            }
            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus
                    + " rectangle=" + rectangle + " ci=" + ci
                    + " vi=" + vi);
            if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
                // Optimization: if the focus hasn't changed since last
                // time, and no layout has happened, then just leave things
                // as they are.
                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y="
                        + mScrollY + " vi=" + vi.toShortString());
            } else {
                // We need to determine if the currently focused view is
                // within the visible part of the window and, if not, apply
                // a pan so it can be seen.
                mLastScrolledFocus = new WeakReference<View>(focus);
                mScrollMayChange = false;
                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
                // Try to find the rectangle from the focus view.
                if (focus.getGlobalVisibleRect(mVisRect, null)) {
                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w="
                            + mView.getWidth() + " h=" + mView.getHeight()
                            + " ci=" + ci.toShortString()
                            + " vi=" + vi.toShortString());
                    if (rectangle == null) {
                        focus.getFocusedRect(mTempRect);
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus
                                + ": focusRect=" + mTempRect.toShortString());
                        if (mView instanceof ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focus, mTempRect);
                        }
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                "Focus in window: focusRect="
                                + mTempRect.toShortString()
                                + " visRect=" + mVisRect.toShortString());
                    } else {
                        mTempRect.set(rectangle);
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                "Request scroll to rect: "
                                + mTempRect.toShortString()
                                + " visRect=" + mVisRect.toShortString());
                    }
                    if (mTempRect.intersect(mVisRect)) {
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                "Focus window visible rect: "
                                + mTempRect.toShortString());
                        if (mTempRect.height() >
                                (mView.getHeight()-vi.top-vi.bottom)) {
                            // If the focus simply is not going to fit, then
                            // best is probably just to leave things as-is.
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Too tall; leaving scrollY=" + scrollY);
                        }
                        // Next, check whether top or bottom is covered based on the non-scrolled
                        // position, and calculate new scrollY (or set it to 0).
                        // We can't keep using mScrollY here. For example mScrollY is non-zero
                        // due to IME, then IME goes away. The current value of mScrollY leaves top
                        // and bottom both visible, but we still need to scroll it back to 0.
                        else if (mTempRect.top < vi.top) {
                            scrollY = mTempRect.top - vi.top;
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Top covered; scrollY=" + scrollY);
                        } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
                            scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Bottom covered; scrollY=" + scrollY);
                        } else {
                            scrollY = 0;
                        }
                        handled = true;
                    }
                }
            }
        }
        if (scrollY != mScrollY) {
            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old="
                    + mScrollY + " , new=" + scrollY);
            if (!immediate) {
                if (mScroller == null) {
                    mScroller = new Scroller(mView.getContext());
                }
                mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
            } else if (mScroller != null) {
                mScroller.abortAnimation();
            }
            mScrollY = scrollY;
        }

        return handled;
    }        

frameworks/base/core/java/android/view/View.java

    public void getDrawingRect(Rect outRect) {
        outRect.left = mScrollX;
        outRect.top = mScrollY;
        outRect.right = mScrollX + (mRight - mLeft);
        outRect.bottom = mScrollY + (mBottom - mTop);
    }

    public void getFocusedRect(Rect r) {
        getDrawingRect(r);
    }

获取当前聚焦的控件的位置信息与当前ViewRootImpl的可见区域进行比对计算出滚动距离。
在绘制的过程中不断更新并计算滚动位置

通过修改mScroller的动画时长,可以清晰看到滚动的过程效果

mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
//改为
mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY, 1000);

在这里插入图片描述
为什么刚好位移 setTranslationY(70) 无法滚动主窗口

		if (mTempRect.intersect(mVisRect)) {
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                "Focus window visible rect: "
                                + mTempRect.toShortString());
                        if (mTempRect.height() >
                                (mView.getHeight()-vi.top-vi.bottom)) {
                            // If the focus simply is not going to fit, then
                            // best is probably just to leave things as-is.
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Too tall; leaving scrollY=" + scrollY);
                        }
                        // Next, check whether top or bottom is covered based on the non-scrolled
                        // position, and calculate new scrollY (or set it to 0).
                        // We can't keep using mScrollY here. For example mScrollY is non-zero
                        // due to IME, then IME goes away. The current value of mScrollY leaves top
                        // and bottom both visible, but we still need to scroll it back to 0.
                        else if (mTempRect.top < vi.top) {
                            scrollY = mTempRect.top - vi.top;
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Top covered; scrollY=" + scrollY);
                        } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
                            scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                    "Bottom covered; scrollY=" + scrollY);
                        } else {
                            scrollY = 0;
                        }
                        handled = true;
                    }

获取的控件的焦点区域和可视区域不存在交集, 导致后续的mScroller部分的代码没有执行。
在TextView中重写了 getFocusedRect,返回的是 光标的坐标,在测试的DEMO中输出如下 [2,10][6,70] 的坐标。

/**
//弹
Need to scroll?
Root w=1920 h=1080 ci=[0,24][0,56] vi=[0,24][0,466]
Focus android.widget.EditText{4146188 VFED..CL. .F..H.I. 0,828-1920,898 #7f03000a app:id/et aid=1073741824}: focusRect=[2,10][6,70]
Focus in window: focusRect=[2,926][6,986] visRect=[0,916][1920,986]
Focus window visible rect: [2,926][6,986]
Bottom covered; scrollY=372
Pan scroll changed: old=0 , new=372

//不弹
Eval scroll: focus=android.widget.EditText{40fcecd VFED..CL. .F..H.I. 0,828-1920,898 #7f03000a app:id/et aid=1073741824} rectangle=null ci=Rect(0, 24 - 
Need to scroll?
Root w=1920 h=1080 ci=[0,24][0,56] vi=[0,24][0,466]
Focus android.widget.EditText{40fcecd VFED..CL. .F..H.I. 0,828-1920,898 #7f03000a app:id/et aid=1073741824}: focusRect=[2,10][6,70]
Focus in window: focusRect=[2,926][6,986] visRect=[0,846][1920,916]

if (mTempRect.intersect(mVisRect)) 对应的两个矩形:

  1. focusRect=[2,926][6,986] visRect=[0,916][1920,986] <-
  2. focusRect=[2,926][6,986] visRect=[0,846][1920,916] <- 不弹,无交集

参考

Android软键盘弹出时把布局顶上去的解决方法
Android EditText默认不弹出输入法的实现方法
5种方法完美解决android软键盘挡住输入框方法详解
Android输入法弹出流程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【GO语言基础】基本数据类型

系列文章目录 【Go语言学习】ide安装与配置 【GO语言基础】前言 【GO语言基础】变量常量 【GO语言基础】数据类型 文章目录 系列文章目录数据类型数值型&#xff1a;整数类型&#xff1a;浮点数类型&#xff1a; 字符型-布尔型-字符串零值转义字符 常用类型转换运算符总结 数据…

matplotlib从起点出发(8)_Tutorial_8_Legend

1 图例教程 在matplotlib中灵活地生成Legend。 本图例指南是legend()中可用文档的扩展——在继续阅读本指南之前&#xff0c;请确保你熟悉legend()文档的内容。 本指南使用了一些常用术语&#xff0c;为清楚起见&#xff0c;此处记录了这些术语&#xff1a; legend entry 图…

【Image captioning】S2 Transformer for Image Captioning 实现流程

S2 Transformer for Image Captioning 实现流程 作者:安静到无声 个人主页 目录 S2 Transformer for Image Captioning 实现流程环境设置数据准备训练评价离线评估在线评估参考文献和引用参考引用致谢推荐专栏环境设置 克隆此存

代码随想录算法训练营第三十二天|122.买卖股票的最佳时机II 55. 跳跃游戏 45.跳跃游戏II

122.买卖股票的最佳时机II 本题解法很巧妙&#xff0c;大家可以看题思考一下&#xff0c;在看题解。 代码随想录 public int maxProfit(int[] prices) {int result 0;for (int i 1; i < prices.length; i) {result Math.max(prices[i] - prices[i - 1], 0);}return re…

【可定制、转换时间戳】解析nc文件,并保存为csv文件

解析nc文件&#xff0c;并保存为csv文件 写在最前面解析nc文件&#xff08;代码汇总放最后面&#xff09;读取nc文件获取气象文件中所有变量解析时间解析部分代码汇总 写入csv文件 写在最前面 愿称之为&#xff1a;支持私人订制、非常完美的版本 参考&#xff1a; 解析部分参…

C 风格文件输入/输出---无格式输入/输出

C 标准库的 C I/O 子集实现 C 风格流输入/输出操作。 <cstdio> 头文件提供通用文件支持并提供有窄和多字节字符输入/输出能力的函数&#xff0c;而 <cwchar>头文件提供有宽字符输入/输出能力的函数。 无格式输入/输出 从文件流获取字符 std::fgetc, std::getc …

TorchDynamo初探②:Torch.FX调研和实践

作者&#xff5c;strint 1 概要 torch.fx 是 PyTorch 官方发布的 Python 到 Python 的代码变换工具。如果你想做 Torch 代码变换&#xff0c;torch.fx 是首选工具。 torch.fx 会将 Torch 代码 trace 成 6 种基础的 node 组成的 graph&#xff0c;基于这个 graph 可以方便的做各…

01 PHP基础知识讲解

一 php基础知识 PHP文件的默认拓展名是“php”。 PHP文件中包含HTML标记、PHP标记、PHP代码以及空格和注释。 PHP标记&#xff1a;开始标记<?php 结束标记 ?> 中间内容是PHP代码。 PHP代码&#xff1a;学习第一个指令 echo 功能是用于输出字符串 。 语句结束符&a…

从零开始-与大语言模型对话学技术-gradio篇(4)

前言 本文介绍「星火杯」认知大模型场景创新赛中的落选项目- AI命理分析系统&#xff0c;属于个人娱乐练手。总结提炼了往期文章精华并发掘出新的知识。 包括本地部署版本和Web在线版本&#xff0c;两种打包方式基于 半自动化使用.bat手动打包迁移python项目 如何把 Gradio …

Minio集群搭建

一、官方文件 1、minio官网 https://min.io/ 2、中文文档 http://docs.minio.org.cn/docs/ 3、集群原理 二、集群部署 1、在每台服务器上创建minio目录 mkdir -p /app/minio/{run,data1,data2} && mkdir -p /etc/minio2、下载或者上传下载好的minio二进制文件 https…

net/http库中request.RemoteAddr的值不确定性-【Golang踩坑笔记】

环境信息&#xff1a; Go 1.20Windows 11 x64 代码示例 // 这里的r是框架传入的request&#xff0c;其中封装了net/http下的request.go中的Request fmt.Println("r.RemoteAddr:", r.RemoteAddr) // 本地执行时,该值可能是[::1]:port也可能是127.0.0.1:port 当在…

安装GPU驱动,CUDA Toolkit和配置与CUDA对应的Pytorch

如果有帮助,记得回来点个赞 目录 1.安装指定GPU驱动如果安装的GPU CUDA Version和CUDA Toolkit版本已经冲突怎么办? 2.安装指定版本的CUDA Toolkit如果我安装了CUDA Toolkit之后nvcc -V仍然显示旧的CUDA Toolkit版本怎么办? 3.安装与CUDA对应的Pytorch 1.安装指定GPU驱动 &…

C++之string

目录 1、了解string 2、string相关函数 3、相关函数的使用 ①构造函数 ②赋值 ③>>&#xff0c;<< ④operator[] ⑤size ⑥iterator(迭代器) ⑦push_back ⑧append ⑨ ⑩capacity ⑪reserve ⑫resize ⑬insert ⑭erase ⑮c_str ⑯find ⑰substr…

简化转换器:使用您理解的单词进行最先进的 NLP — 第 1 部分 — 输入

一、说明 变形金刚是一种深度学习架构&#xff0c;为人工智能的发展做出了杰出贡献。这是人工智能和整个技术领域的一个重要阶段&#xff0c;但也有点复杂。截至今天&#xff0c;变形金刚上有很多很好的资源&#xff0c;那么为什么要再制作一个呢&#xff1f;两个原因&#xff…

代码随想录第43天|416. 分割等和子集,1049. 最后一块石头的重量 II, ​494.目标和,​ 474.一和零(一窍不通)

416. 分割等和子集 思路 本题是01背包的应用题 背包的体积为sum / 2背包要放入的商品&#xff08;集合里的元素&#xff09;重量为 元素的数值&#xff0c;价值也为元素的数值背包如果正好装满&#xff0c;说明找到了总和为 sum / 2 的子集。背包中每一个元素是不可重复放入…

(其他) 剑指 Offer 67. 把字符串转换成整数 ——【Leetcode每日一题】

❓ 剑指 Offer 67. 把字符串转换成整数 难度&#xff1a;中等 写一个函数 StrToInt&#xff0c;实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。 首先&#xff0c;该函数会根据需要丢弃无用的开头空格字符&#xff0c;直到寻找到第一个非空格的字符为…

C++学习笔记(堆栈、指针、命名空间、编译步骤)

C 1、堆和栈2、指针2.1、指针的本质2.2、指针的意义2.3、清空指针2.4、C类中的this 3、malloc and new4、命名空间4.1、创建命名空间4.2、使用命名空间 5、编译程序的四个步骤5.1、预处理5.2、编译5.3、汇编5.4、链接 1、堆和栈 堆&#xff08;heap&#xff09;和栈&#xff0…

领域驱动设计:DDD与微服务的关系

文章目录 基础概念软件架构模式的演进微服务设计和拆分的困境为什么 DDD 适合微服务&#xff1f;DDD 与微服务的关系 基础概念 DDD 虽然历史很久了&#xff0c;但它与微服务和中台设计的结合&#xff0c;却是一片很新的领域。早在 2003 年就诞生的 DDD&#xff0c;怎么来指导“…

基于spring boot+ vue开发的位置数据展现和分析平台源码 UWB源码

spring boot vue位置数据展现和分析平台源码 UWB室内外高精度定位系统源码 智慧工厂是现代工厂信息化发展的新阶段&#xff0c;基于UWB定位技术&#xff0c;融合位置物联网、GIS可视化等技术&#xff0c;实现对人员、物资精确管理。在重点区域设置电子围栏&#xff0c;无权限…

【Mysql】数据库第一讲(服务器数据库的安装和基础操作介绍)

数据库基础 &#x1f361;1.CentOs服务器数据库的安装&#x1f367;2.基础使用&#x1f368; 2.1 服务器&#xff0c;数据库&#xff0c;表关系&#x1f366;2.2使用案例&#xff1a; &#x1f967;3.数据库分类&#x1f9c1;4.存储引擎&#x1f370;4.Mysql库的操作&#x1f3…