欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 动态改变布局实例详解

程序员文章站 2024-03-02 10:33:10
android 动态改变布局            &nbs...

android 动态改变布局

               最近项目需求,动态的改变布局,为了增加客户体验,尤其是在输入框出现小键盘的时候,为了避免小键盘遮挡app内容就需要动态改变布局:

                先看下实现效果图:

Android 动态改变布局实例详解Android 动态改变布局实例详解

其实是一个软件的登录界面,初始是第一个图的样子,当软键盘弹出后变为第二个图的样子,因为登录界面有用户名、密码、登录按钮,不这样的话软键盘弹出后会遮住登录按钮(其实之前的实现放到了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;
 }

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

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!