Android動態改變布局

來源:互聯網
上載者:User

標籤:android   des   style   blog   http   color   

遇到這麼個需求,先看圖:

    

其實是一個軟體的登入介面,初始是第一個圖的樣子,當軟鍵盤彈出後變為第二個圖的樣子,因為登入介面有使用者名稱、密碼、登入按鈕,不這樣的話軟鍵盤彈出後會遮住登入按鈕(其實之前的實現放到了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)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.