Android 动态改变布局实例详解

Android 动态改变布局

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

先看下实现效果图:

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

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

(0)

相关推荐

  • Android RefreshLayout实现下拉刷新布局

    项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个. 于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦 为了省事,headView还是用了XListView的HeadView,省了很多事:) 下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为

  • Android实现的ListView分组布局改进示例

    本文实例讲述了Android实现的ListView分组布局改进方法.分享给大家供大家参考,具体如下: 由于是在网上转载的一篇文章,在这里就不多说废话了,首先看一下最终的效果图: 然后是实现该ListView布局的主要代码: 1.程序主界面 SeparateListView.java package whu.iss.wuxianglong; import java.util.ArrayList; import java.util.List; import android.app.Activity;

  • Android 五大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(TableLayout):按照行列方式布局组件. 相对布局(RelativeLayout):相对其它组件的布局方式.  绝对布局(AbsoluteLayout):按照绝对坐标来布局组件.  1. 线性布局 线性布局是Android开发中最常见的一种布局方式,它是按照垂直或者水平方向来布局,通过"androi

  • 探究Android中ListView复用导致布局错乱的解决方案

    首先来说一下具体的需求是什么样的: 需求如图所示,这里面有ABCD四个选项的题目,当点击A选项,如果A是正确的答案,则变成对勾的图案,如果是错误答案,则变成错误的图案,这里当时在写的时候觉得很简单,只要是在点击的时候判断我点击的选项与正确答案是否一样,是一样就将图片换成正确的样式,如果不一样就换成错误的样式,于是我便写了下面的代码(只贴出了核心Adapter中的代码) package com.fizzer.anbangproject_dahuo_test.Adapter; import andr

  • Android实现气泡布局/弹窗效果 气泡尖角方向及偏移量可控

    Android 自定义布局实现气泡弹窗,可控制气泡尖角方向及偏移量. 效果图 实现 首先自定义一个气泡布局. /** * 气泡布局 */ public class BubbleRelativeLayout extends RelativeLayout { /** * 气泡尖角方向 */ public enum BubbleLegOrientation { TOP, LEFT, RIGHT, BOTTOM, NONE } public static int PADDING = 30; public

  • Android动画效果之自定义ViewGroup添加布局动画(五)

    前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通过对自定义图片选择控件设置动画为例来学习布局动画. 自定义一个显示多行图片的ViewGroup: 这里不再对自定义控件做解说,想了解的可以看下以下几篇文章  •Android自定义控件之基本原理(一)  •Android自定义控件之自定义属性(二)  •Android自定义控件之自定义组合控件(三)

  • Android布局实现圆角边框效果

    首先,在res下面新建一个文件夹drawable,在drawable下面新建三个xml文件:shape_corner_down.xml.shape_corner_up.xml和shape_corner.xml,分别是下面两个角是圆角边框,上面两个角是圆角边框,四个角全部是圆角边框. shape_corner_down.xml: <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=&

  • Android ListView自动显示隐藏布局的实现方法

    借助View的OnTouchListener接口来监听listView的滑动,通过比较与上次坐标的大小,判断滑动方向,并通过滑动方向来判断是否需显示或者隐藏对应的布局,并且带有动画效果. 1.自动显示隐藏Toolbar 首先给listView增加一个HeaderView,避免第一个Item被Toolbar遮挡. View header=new View(this); header.setLayoutParams(new AbsListView.LayoutParams( AbsListView.

  • Android RecyclerView加载不同布局简单实现

    前言 关于RecyclerView的使用这里就不在赘述了,相信网上一搜一大把(本人之前的文章也有简单的使用介绍),这次我们讲的是RecyclerView在使用的过程中,有时候会根据不同的位置加载不同的布局的简单实现,这里只是起到抛砖引玉的作用 效果图 设计思想  •重写RecyclerView.Adapter的getItemViewType(int position), 在此方法中根据不同的position,设置不同的ViewType  •编写具体的RecyclerView.ViewHolder

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

    Android 动态改变布局                最近项目需求,动态的改变布局,为了增加客户体验,尤其是在输入框出现小键盘的时候,为了避免小键盘遮挡APP内容就需要动态改变布局: 先看下实现效果图: 其实是一个软件的登录界面,初始是第一个图的样子,当软键盘弹出后变为第二个图的样子,因为登录界面有用户名.密码.登录按钮,不这样的话软键盘弹出后会遮住登录按钮(其实之前的实现放到了ScrollView里面,监听软键盘弹出后滚动到底部,软键盘隐藏后滚动到顶部,也是可以的). 最简单的方法就是多

  • Android开发高仿课程表的布局实例详解

    先说下这个demo,这是一个模仿课程表的布局文件,虽然我是个菜鸟,但我还是想留给学习的人一些例子,先看下效果 然后再来看一下我们学校的app 布局分析 先上一张划分好了的布局图 首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 1:这个没什么好讲的,就是直接一个LinearLayout布局,然后将控件一个TextView用来显示年份,一个View用来当作竖线,一个Spinner用来显示选择周数 2:这个是显示星期几的部件,是我自定义的V

  • Android原生音量控制实例详解

    本文主要涉及AudioService.还是基于5.1.1版本的代码. AudioService.java文件位于/framework/base/media/java/android/media/下. 音量控制是AudioService最重要的功能之一.先总结一下: AudioService音量管理的核心是VolumeStreamState.它保存了一个流类型所有的音量信息. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的.所以进行音

  • Android Fragment的用法实例详解

    碎片,它的出现是为了更好展示UI的设计,让程序更加得到充分的展示.Fragment的出现,如微信的额主界面包含多个Fragment,使得微信功能更加简洁明了. Fragment组件 Fragment是Android 3.0的时候被引入的,主要目的是为了给大屏幕(如平板电脑)添加动态和灵活的UI支持.利用Fragment实现更好的用户体验. Fragment加载 1.静态加载:添加Fragment到Activity布局中,以xml的形式. 2.动态加载: <LinearLayout android

  • Android 帧动画的实例详解

    Android 帧动画的实例详解 对于 Android 帧动画 大体上可以理解成 一张张图片 按一定顺序切换, 这样当连续几张图是一组动画时,就可以连起来了看成是一个小电影,你懂得 好得,比就装到这里,下面开始进入正题,由于产品需求 需要做一个 声音喇叭动态切换的样式,我特么第一就想到是帧动画切换,然后就百度了一些资料,发现 真的, 现在这个网上太多的资料是 copy粘贴过来的, 一错全错,对于这种情况我只想说,made,一群垃圾, 所以今天我将带你们走进Android 正确帧动画地址. 第一步

  • Android的搜索框架实例详解

    基础知识 Android的搜索框架将代您管理的搜索对话框,您不需要自己去开发一个搜索框,不需要担心要把搜索框放什么位置,也不需要担心搜索框影响您当前的界面.所有的这些工作都由SearchManager类来为您处理(以下简称"搜索管理器"),它管理的Android搜索对话框的整个生命周期,并执行您的应用程序将发送的搜索请求,返回相应的搜索关键字. 当用户执行一个搜索,搜索管理器将使用一个专门的Intent把搜索查询的关键字传给您在配置文件中配置的处理搜索结果的Activity.从本质上讲

  • Android 自定义标题栏的实例详解

     Android 自定义标题栏的实例详解 开发 Android APP 经常会用到自定义标题栏,而有多级页面的情况下还需要给自定义标题栏传递数据. 本文要点: 自定义标题填充不完整 自定义标题栏返回按钮的点击事件 一.代码 这里先介绍一下流程: 1. 创建一个标题栏布局文件 mytitlebar.xml 2. 在style.xml中创建 mytitlestyle 主题 3. 创建类 CustomTitleBar 4. 在需要自定义标题栏的Activity的OnCreate方法中实例化 Custo

  • Android PhotoView使用步骤实例详解

    Android PhotoView使用步骤实例详解 1.步骤一:在布局文件中的代码: <uk.co.senab.photoview.PhotoView android:id="@+id/img_showimgview" android:layout_width="match_parent" android:layout_height="match_parent" /> 2.步骤二:进行找到控件的id photoview = (Phot

  • Android图片压缩的实例详解

    Android图片压缩的实例详解 在做微信分享的时候,由于分享的缩略图要求不得大于32K,否则不能调起微信,所以总结了一下Android图片的压缩问题,大部分资料都是来自网上各位的分享,自己只是完善或修改了一下,本着继续分享的精神,也方便自己记忆,于是总结如下. android图片压缩主要有两种方式:1.压缩图片分辨率 2.压缩图片质量 一.先看压缩图片分辨率,很好理解,如本来1280*768的图片压缩为640*384大小.废话不说,直接上代码: /** * 按比例压缩图片分辨率 * @para

  • Android开发之Android.mk模板的实例详解

    Android开发之Android.mk模板的实例详解 关于Android NDK开发的文章已经比较多了,我的博客中也分享了很多NDK开发相关经验和技巧,今天简单写了一个 Android.mk 的示例模板,供初学者参考. 本模板主要给大家示例 Android NDK 开发中的如下几个问题: 1. 如何自动添加需要编译的源文件列表   2. 如何添加第三方静态库.动态库的依赖   3. 如何构造一个完整的NDK工程框架 假设我们的项目依赖 libmath.a, libjson.a, libffmp

随机推荐