Android 動態(tài)改變布局實(shí)例詳解
Android 動態(tài)改變布局
最近項目需求,動態(tài)的改變布局,為了增加客戶體驗(yàn),尤其是在輸入框出現(xiàn)小鍵盤的時候,為了避免小鍵盤遮擋APP內(nèi)容就需要動態(tài)改變布局:
先看下實(shí)現(xiàn)效果圖:


其實(shí)是一個軟件的登錄界面,初始是第一個圖的樣子,當(dāng)軟鍵盤彈出后變?yōu)榈诙€圖的樣子,因?yàn)榈卿浗缑嬗杏脩裘?、密碼、登錄按鈕,不這樣的話軟鍵盤彈出后會遮住登錄按鈕(其實(shí)之前的實(shí)現(xiàn)放到了ScrollView里面,監(jiān)聽軟鍵盤彈出后滾動到底部,軟鍵盤隱藏后滾動到頂部,也是可以的)。
最簡單的方法就是多加幾個冗余的View,根據(jù)軟鍵盤的狀態(tài)隱藏不需要的View,顯示需要的View,但這樣感覺太挫了,然后就想起了前兩年研究的RelativeLayout布局,RelativeLayout中子控件的布局都是相對位置,只需要在軟鍵盤彈出隱藏時改變應(yīng)用的位置規(guī)則就行了。
先來看一下布局文件
<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監(jiān)聽實(shí)現(xiàn),對Activity應(yīng)用android:windowSoftInputMode="stateHidden|adjustResize",這樣開始時軟鍵盤不顯示,當(dāng)軟鍵盤彈出時布局被Resize。
接下來是代碼,所有的代碼都在這里了
public class MainActivity extends Activity {
private View root; // 最外層布局
private View logo; // Logo圖標(biāo)
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);
// 進(jìn)入Activity時會布局,第一次調(diào)用onGlobalLayout,先記錄開始軟鍵盤沒有彈出時底部的位置
if (rootBottom == Integer.MIN_VALUE) {
rootBottom = r.bottom;
return;
}
// adjustResize,軟鍵盤彈出后高度會變小
if (r.bottom < rootBottom) {
RelativeLayout.LayoutParams lp = (LayoutParams) logo.getLayoutParams();
// 如果Logo不是水平居中,說明是因?yàn)榻酉聛淼母淖僉ogo大小位置導(dǎo)致的再次布局,忽略掉,否則無限循環(huán)
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();
// 如果沒有水平居中,說明是軟鍵盤收起,否則是開始時的初始化或者因?yàn)榇颂巌f條件里的語句修改控件導(dǎo)致的再次布局,忽略掉,否則無限循環(huán)
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); // 設(shè)置水平居中
labelParams.addRule(RelativeLayout.BELOW, R.id.logo); // 設(shè)置顯示到Logo下面
labelParams.addRule(RelativeLayout.RIGHT_OF, 0); // 取消顯示到Logo右面
labelParams.addRule(RelativeLayout.CENTER_VERTICAL, 0); // 取消垂直居中
label.setLayoutParams(labelParams);
}
}
}
});
}
}
當(dāng)Activity啟動時也會進(jìn)行Layout,此時用rootBottom記錄了初始時最外層布局底部的位置,此后當(dāng)軟鍵盤彈出時,布局被壓縮,再次獲取同一個View底部的位置,如果比rootBottom小說明軟鍵盤彈出了,如果大于或等于rootBottom說明軟鍵盤隱藏了。
所有的代碼都在上面,也有詳細(xì)注釋,有兩點(diǎn)需要注意一下:
1.Activity啟動時會進(jìn)行Layout,此時會調(diào)用onGlobalLayout,而且一般會調(diào)用兩次,這樣第二次時會進(jìn)入else語句,要注意過濾
2.軟鍵盤彈出或隱藏時進(jìn)入onGlobalLayout,此時根據(jù)需要縮放Logo的大小,并改變Logo和Label的位置,這些操作會引起再次onGlobalLayout,需要將之后的onGlobalLayout過濾掉,不然就無限循環(huán)了。
可以看到上面代碼中的過濾條件,以else語句中的為例,Activity啟動時會進(jìn)入else,此時Logo是水平居中狀態(tài),會跳過else里面的if語句,這樣就處理掉了第一種情況。
當(dāng)因?yàn)檐涙I盤收起進(jìn)入else時,Logo已經(jīng)因?yàn)閕f語句塊變?yōu)榱孙@示在左上角,所以會進(jìn)入else中的if語句,重新改變Logo為水平居中,由于修改了Logo的大小和位置,會導(dǎo)致再次進(jìn)入onGlobalLayout,仍是進(jìn)入else,但此時已經(jīng)設(shè)置Logo為水平居中了,不會再次進(jìn)入else中的if語句,這樣通過一個條件判斷就處理了上面提到的兩點(diǎn)注意事項。
關(guān)于addRule
RelativeLayout中每一個子控件所應(yīng)用的規(guī)則都是通過數(shù)組保存的,如下所示:
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;
}
以某一規(guī)則的索引為下標(biāo),值就是規(guī)則對應(yīng)的anchor,如果是相對于另一個子控件,值就是另一個子控件的ID,如果是相對于父控件,值就是`TRUE`,即-1,如果沒有應(yīng)用某一規(guī)則值就是0,可以看到,removeRule就是把相應(yīng)位置的值改為了0:
public void removeRule(int verb) {
mRules[verb] = 0;
mInitialRules[verb] = 0;
mRulesChanged = true;
}
removeRule是API 17才加的方法,為了在API 17前也能使用,可以使用它的等價方法,像上面的例子中的一樣,使用addRule(verb, 0)。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
Android?Flutter在點(diǎn)擊事件上添加動畫效果實(shí)現(xiàn)全過程
這篇文章主要給大家介紹了關(guān)于Android?Flutter在點(diǎn)擊事件上添加動畫效果實(shí)現(xiàn)的相關(guān)資料,通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)Android具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03
深入Android HandlerThread 使用及其源碼完全解析
這篇文章主要介紹了深入Android HandlerThread 使用及其源碼完全解析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Android串口開發(fā)之使用JNI實(shí)現(xiàn)ANDROID和串口通信詳解
這篇文章主要給大家介紹了關(guān)于Android串口開發(fā)之使用JNI實(shí)現(xiàn)ANDROID和串口通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
Android編程實(shí)現(xiàn)應(yīng)用強(qiáng)制安裝到手機(jī)內(nèi)存的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)應(yīng)用強(qiáng)制安裝到手機(jī)內(nèi)存的方法,涉及Android中屬性設(shè)置的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
android 實(shí)現(xiàn)控件左右或上下抖動教程
這篇文章主要介紹了android 實(shí)現(xiàn)控件左右或上下抖動教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
如何使用Kotlin進(jìn)行Android開發(fā)
這篇文章主要教大家如何使用Kotlin進(jìn)行Android開發(fā),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05

