遇到这么个需求,先看图:

    

其实是一个软件的登录界面,初始是第一个图的样子,当软键盘弹出后变为第二个图的样子,因为登录界面有用户名、密码、登录按钮,不这样的话软键盘弹出后会遮住登录按钮(其实之前的实现放到了ScrollView里面,监听软键盘弹出后滚动到底部,软键盘隐藏后滚动到顶部,也是可以的)。

最简单的方法就是多加几个冗余的View,根据软键盘的状态隐藏不需要的View,显示需要的View,但这样感觉太挫了,然后就想起了前两年研究的RelativeLayout布局,RelativeLayout中子控件的布局都是相对位置,只需要在软键盘弹出隐藏时改变应用的位置规则就行了。

先来看一下布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
tools:context="${packageName}.${activityClass}" > <RelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" > <ImageView
android:id="@+id/logo"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerHorizontal="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher"
tools:ignore="ContentDescription" /> <TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/logo"
android:layout_centerHorizontal="true"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:text="@string/hello_world"
android:textSize="20sp" />
</RelativeLayout> <EditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/container"
android:layout_margin="16dp"
android:hint="Input sth."
tools:ignore="HardcodedText" /> </RelativeLayout>

软键盘的弹出隐藏用OnGlobalLayoutListener监听实现,对Activity应用android:windowSoftInputMode="stateHidden|adjustResize",这样开始时软键盘不显示,当软键盘弹出时布局被Resize。

接下来是代码,所有的代码都在这里了

public class MainActivity extends Activity {

    private View root; // 最外层布局
private View logo; // Logo图标
private View label; // Logo附近的文字 private int rootBottom = Integer.MIN_VALUE; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
root = findViewById(R.id.root);
logo = findViewById(R.id.logo);
label = findViewById(R.id.label);
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override
public void onGlobalLayout() {
Rect r = new Rect();
root.getGlobalVisibleRect(r);
// 进入Activity时会布局,第一次调用onGlobalLayout,先记录开始软键盘没有弹出时底部的位置
if (rootBottom == Integer.MIN_VALUE) {
rootBottom = r.bottom;
return;
}
// adjustResize,软键盘弹出后高度会变小
if (r.bottom < rootBottom) {
RelativeLayout.LayoutParams lp = (LayoutParams) logo.getLayoutParams();
// 如果Logo不是水平居中,说明是因为接下来的改变Logo大小位置导致的再次布局,忽略掉,否则无限循环
if (lp.getRules()[RelativeLayout.CENTER_HORIZONTAL] != 0) {
// Logo显示到左上角
lp.addRule(RelativeLayout.CENTER_HORIZONTAL, 0); // 取消水平居中
lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT); // 左对齐 // 缩小Logo为1/2
int height = logo.getHeight(); // getMeasuredHeight()
int width = logo.getWidth();
lp.width = width / 2;
lp.height = height / 2;
logo.setLayoutParams(lp); // Logo下的文字
RelativeLayout.LayoutParams labelParams = (LayoutParams) label.getLayoutParams();
labelParams.addRule(RelativeLayout.CENTER_HORIZONTAL, 0); // 取消水平居中
labelParams.addRule(RelativeLayout.BELOW, 0); // 取消显示到logo的下方
labelParams.addRule(RelativeLayout.RIGHT_OF, R.id.logo); // 显示到Logo的右方
labelParams.addRule(RelativeLayout.CENTER_VERTICAL); // 垂直居中
label.setLayoutParams(labelParams);
}
} else { // 软键盘收起或初始化时
RelativeLayout.LayoutParams lp = (LayoutParams) logo.getLayoutParams();
// 如果没有水平居中,说明是软键盘收起,否则是开始时的初始化或者因为此处if条件里的语句修改控件导致的再次布局,忽略掉,否则无限循环
if (lp.getRules()[RelativeLayout.CENTER_HORIZONTAL] == 0) {
// 居中Logo
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0); // 还原Logo为原来大小
int height = logo.getHeight();
int width = logo.getWidth();
lp.width = width * 2;
lp.height = height * 2;
logo.setLayoutParams(lp); // Logo下的文字
RelativeLayout.LayoutParams labelParams = (LayoutParams) label.getLayoutParams();
labelParams.addRule(RelativeLayout.CENTER_HORIZONTAL); // 设置水平居中
labelParams.addRule(RelativeLayout.BELOW, R.id.logo); // 设置显示到Logo下面
labelParams.addRule(RelativeLayout.RIGHT_OF, 0); // 取消显示到Logo右面
labelParams.addRule(RelativeLayout.CENTER_VERTICAL, 0); // 取消垂直居中
label.setLayoutParams(labelParams);
}
}
}
});
}
}

当Activity启动时也会进行Layout,此时用rootBottom记录了初始时最外层布局底部的位置,此后当软键盘弹出时,布局被压缩,再次获取同一个View底部的位置,如果比rootBottom小说明软键盘弹出了,如果大于或等于rootBottom说明软键盘隐藏了。

所有的代码都在上面,也有详细注释,有两点需要注意一下:

  1. Activity启动时会进行Layout,此时会调用onGlobalLayout,而且一般会调用两次,这样第二次时会进入else语句,要注意过滤
  2. 软键盘弹出或隐藏时进入onGlobalLayout,此时根据需要缩放Logo的大小,并改变Logo和Label的位置,这些操作会引起再次onGlobalLayout,需要将之后的onGlobalLayout过滤掉,不然就无限循环了。

可以看到上面代码中的过滤条件,以else语句中的为例,Activity启动时会进入else,此时Logo是水平居中状态,会跳过else里面的if语句,这样就处理掉了第一种情况。

当因为软键盘收起进入else时,Logo已经因为if语句块变为了显示在左上角,所以会进入else中的if语句,重新改变Logo为水平居中,由于修改了Logo的大小和位置,会导致再次进入onGlobalLayout,仍是进入else,但此时已经设置Logo为水平居中了,不会再次进入else中的if语句,这样通过一个条件判断就处理了上面提到的两点注意事项。

关于addRule

RelativeLayout中每一个子控件所应用的规则都是通过数组保存的,如下所示:

public static final int TRUE = -1;

public void addRule(int verb) {
mRules[verb] = TRUE;
mInitialRules[verb] = TRUE;
mRulesChanged = true;
} public void addRule(int verb, int anchor) {
mRules[verb] = anchor;
mInitialRules[verb] = anchor;
mRulesChanged = true;
}

以某一规则的索引为下标,值就是规则对应的anchor,如果是相对于另一个子控件,值就是另一个子控件的ID,如果是相对于父控件,值就是`TRUE`,即-1,如果没有应用某一规则值就是0,可以看到,removeRule就是把相应位置的值改为了0:

public void removeRule(int verb) {
mRules[verb] = 0;
mInitialRules[verb] = 0;
mRulesChanged = true;
}

removeRuleAPI 17才加的方法,为了在API 17前也能使用,可以使用它的等价方法,像上面的例子中的一样,使用addRule(verb, 0)

Android动态改变布局的更多相关文章

  1. Android 动态改变布局属性RelativeLayout.LayoutParams.addRule()

    我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...

  2. 代码中动态改变布局属性RelativeLayout.LayoutParams.addRule()

    我们知道,在 RelativeLayout 布局中有很多特殊的属性,通常在载入布局之前,在相关的xml文件中进行静态设置即可. 但是,在有些情况下,我们需要动态设置布局的属性,在不同的条件下设置不同的 ...

  3. Android 动态改变高度以及计算长度的EditText

    前段时间项目需求,需要做一个有限制长度的输入框并动态显示剩余文字,同时也要动态改变EditText的高度来增加用户体验.现整理出来与大家分享. 先来看看效果图 看了效果就分享一下布局 <Rela ...

  4. android 动态改变listview的内容

    本文模拟:点击一个按钮,为已有的listview添加一行数据 <?xml version="1.0" encoding="utf-8"?> < ...

  5. 【转】Android动态改变对 onCreateDialog话框值 -- 不错不错!!!

    原文网址:http://www.111cn.net/sj/android/46484.htm 使用方法是这样的,Activity.showDialog()激发Activity.onCreateDial ...

  6. android 动态改变控件位置和大小 .

    动态改变控件位置的方法: setPadding()的方法更改布局位置. 如我要把Imageview下移200px:             ImageView.setPadding( ImageVie ...

  7. Android 动态生成布局 (多层嵌套)

    Android 除了能够载入xml文件,显示布局外,也能够代码生成布局,并通过setContentView(View view)方法显示布局.单独的一层布局,如一个主布局加一个控件(如Button\i ...

  8. Android动态改变App在Launcher里面的icon

    如果呆萌的产品童鞋让你动态更换App在Launcher里面的Icon,你怎么回答他,下文就提出一种实现该效果的方法. 原理1--activity-alias 在AndroidMainifest中,有两 ...

  9. Android -- 动态添加布局

    在做项目的时候,遇到了scrollView与listView结合的使用,导致了滑动的混乱,但是有一个办法可以解决掉这个问题,就是手写listView的高度,还有另外一种方法,传送门:<Andro ...

随机推荐

  1. ABP文档 - 导航

    文档目录 本节内容: 创建菜单 注册导航供应器 显示菜单 每个web应用都有一些菜单用来在页面/屏幕之间导航,ABP提供了一个通用的基础框架创建并显示菜单给用户. 创建菜单 一个应用可能由不同模块组成 ...

  2. 安卓易学,爬坑不易——腾讯老司机的RecyclerView局部刷新爬坑之路

    针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验.目前功能还在免费开放中. 点击地址:http://wetest ...

  3. CGI与FastCGI nginx+PHP-FPM

    本文转载自CGI与FastCGI 1.当我们在谈到cgi的时候,我们在讨论什么 最早的Web服务器简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态html. ...

  4. bzoj3207--Hash+主席树

    题目大意: 给定一个n个数的序列和m个询问(n,m<=100000)和k,每个询问包含k+2个数字:l,r,b[1],b[2]...b[k],要求输出b[1]~b[k]在[l,r]中是否出现. ...

  5. postgresql 基本语法

    postgresql数据库创建/修改/删除等写入类代码语法总结: 1,创建库 2,创建/删除表 2.1 创建表 create table myTableName 2.2 如果表不存在则创建表 crea ...

  6. 清除打印机队列中无法清除的任务 & 清空打印池

    故障现象典型表现为以下两种情况 1.当打印任务开始进行时,这些打印任务便被保存在打印作业列表(也称打印队列)内.如果打印机因意外暂停(如打印机未连接)而未完成打印任务,则该打印任务将列入打印队列,并且 ...

  7. OpenGL: 纹理采样 texture sample

    Sampler (GLSL) Sampler通常是在Fragment shader(片元着色器)内定义的,这是一个uniform类型的变量,即处理不同的片元时这个变量是一致不变的.一个sampler和 ...

  8. mono for android 自定义titleBar Actionbar 顶部导航栏 修改 样式 学习

    以前的我是没有做笔记的习惯的,学习了后觉得自己能记住,但是最近发现很多学的东西都忘记了,所有现在一有新的知识,就记下来吧. 最近又做一个mono for android 的项目 这次调整比较大,上次做 ...

  9. 让OMCS支持更多的视频采集设备

    有些OMCS用户在他的系统使用了特殊的视频采集卡作为视频源(如AV-878采集卡),虽然这些采集卡可以虚拟为一个摄像头,但有些视频采集卡需要依赖于自带了sdk才能正常地完成视频采集工作.在这种情况下, ...

  10. 在同一个硬盘上安装多个 Linux 发行版及 Fedora 21 、Fedora 22 初体验

    在同一个硬盘上安装多个 Linux 发行版 以前对多个 Linux 发行版的折腾主要是在虚拟机上完成.我的桌面电脑性能比较强大,玩玩虚拟机没啥问题,但是笔记本电脑就不行了.要在我的笔记本电脑上折腾多个 ...