首页 > 代码库 > Android动态改变布局
Android动态改变布局
遇到这么个需求,先看图:
事实上是一个软件的登录界面,初始是第一个图的样子,当软键盘弹出后变为第二个图的样子,由于登录界面实username、password、登录button,不这种话软键盘弹出后会遮住登录button(事实上之前的实现放到了ScrollView里面,监听软键盘弹出后滚动究竟部,软键盘隐藏后滚动到顶部,也是能够的)。
最简单的方法就是多加几个冗余的View,依据软键盘的状态隐藏不须要的View,显示须要的View,但这样感觉太挫了,然后就想起了前两年研究的RelativeLayout布局,RelativeLayout中子控件的布局都是相对位置,仅仅须要在软键盘弹出隐藏时改变应用的位置规则即可了。
先来看一下布局文件
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说明软键盘隐藏了。
全部的代码都在上面,也有具体凝视,有两点须要注意一下:
- Activity启动时会进行Layout,此时会调用onGlobalLayout,并且通常会调用两次,这样第二次时会进入else语句,要注意过滤
- 软键盘弹出或隐藏时进入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; }
removeRule是API 17才加的方法,为了在API 17前也能使用。能够使用它的等价方法,像上面的样例中的一样,使用addRule(verb, 0)。
Android动态改变布局