《第一行代碼:Android篇》學習筆記(三)

来源:https://www.cnblogs.com/1693977889zz/archive/2022/05/11/16256339.html
-Advertisement-
Play Games

本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...


本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。
每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激感激郭霖先生提供這麼好的書籍。

第3章 軟體也要拼臉蛋——UI開發的點點滴滴

Android也給我們提供了大量的UI開發工具,只要合理地使用它們,就可以編寫出各種各樣漂亮的界面。在這裡,我無法教會你如何提升自己的審美觀,但我可以教會你怎樣使用Android提供的UI開發工具來編寫程式界面。

3.1 如何編寫程式界面

Android中有多種編寫程式界面的方式可供選擇。Android Studio和Eclipse中都提供了相應的可視化編輯器,允許使用拖放控制項的方式來編寫佈局,並能在視圖上直接修改控制項的屬性。

不過我並不推薦你使用這種方式來編寫界面,因為可視化編輯工具並不利於你去真正瞭解界面背後的實現原理。通過這種方式製作出的界面通常不具有很好的屏幕適配性,而且當需要編寫較為複雜的界面時,可視化編輯工具將很難勝任。

因此本書中所有的界面都將通過最基本的方式去實現,即編寫XML代碼。等你完全掌握了使用XML來編寫界面的方法之後,不管是進行高複雜度的界面實現,還是分析和修改當前現有界面,對你來說都將是手到擒來。

下麵我們就從Android中幾種常見的控制項開始吧。

3.2 常用控制項的使用方法

Android提供了大量的UI控制項,合理地使用這些控制項就可以非常輕鬆地編寫出相當不錯的界面,下麵就挑選幾種常用的控制項,詳細介紹一下它們的使用方法。

首先新建一個UIWidgetTest項目,我們還是允許Android Studio自動創建活動,活動名和佈局名都使用預設值。

3.2.1 TextView

修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is TextView"/>
</LinearLayout>
  • 在TextView中我們使用android:id給當前控制項定義了一個唯一標識符

  • 用android:layout_width和android:layout_height指定了控制項的寬度和高度

    Android中所有的控制項都具有這兩個屬性,可選值有3種:match_parent、fill_parent和wrap_content。

    • match_parent和fill_parent的意義相同,現在官方更加推薦使用match_parent。
    • match_parent表示讓當前控制項的大小和父佈局的大小一樣,也就是由父佈局來決定當前控制項的大小。
    • wrap_content表示讓當前控制項的大小能夠剛好包含住裡面的內容,由控制項內容決定當前控制項的大小。

    所以,上面的代碼就表示讓TextView的寬度和父佈局一樣寬,也就是手機屏幕的寬度,讓TextView的高度足夠包含住裡面的內容就行。當然除了使用上述值,你也可以對控制項的寬和高指定一個固定的大小,但是這樣做有時會在不同手機屏幕的適配方面出現問題。

    接下來我們通過android:text指定TextView中顯示的文本內容,現在運行程式,效果如圖:

image

由於TextView中的文字預設是居左上角對齊的,雖然TextView的寬度充滿了整個屏幕,可是由於文字內容不夠長,所以從效果上完全看不出來。修改TextView的文字對齊方式,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="This is TextView"/>
</LinearLayout>

使用android:gravity來指定文字的對齊方式,可選值有top、bottom、left、right、center等,可以用“|”來同時指定多個值,這裡我們指定的center,效果等同於center_vertical|center_horizontal,表示文字在垂直和水平方向都居中對齊。現在重新運行程式,效果如圖:

image

這也說明瞭TextView的寬度確實是和屏幕寬度一樣的。另外還可以對TextView中文字的大小和顏色進行修改,如下所示:

 <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#00ff00"
        android:text="This is TextView"/>

當然TextView中還有很多其他的屬性,這裡就不再一一介紹了,用到的時候去查閱文檔就可以了。

3.2.2 Button

Button是程式用於和用戶進行交互的一個重要控制項,可以在activity_main.xml中這樣加入Button:

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="Button"/>

image

你可能會留意到,我們在佈局文件裡面設置的文字是“Button”,但最終的顯示結果卻是“BUTTON”。這是由於系統會對Button中的所有英文字母自動進行大寫轉換,如果這不是你想要的效果,可以使用如下配置來禁用這一預設特性:

<Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="Button"
        android:textAllCaps="false"/>

接下來可以在MainActivity中為Button的點擊事件註冊一個監聽器,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //此處添加邏輯
            }
        });
    }
}

每當點擊按鈕時,就會執行監聽器中的onClick()方法,我們只需要在這個方法中加入待處理的邏輯就行了。如果你不喜歡使用匿名類的方式來註冊監聽器,也可以使用實現介面的方式來進行註冊,代碼如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }
    //實現View.OnClickListener介面
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                break;
            default:
                break;
        }
    }
}

3.2.3 EditText

EditText是程式用於和用戶進行交互的另一個重要控制項,它允許用戶在控制項里輸入和編輯內容,並可以在程式中對這些內容進行處理。EditText的應用場景非常普遍,在進行發簡訊、發微博、聊QQ等操作時,使用EditText。修改activity_main.xml中的代碼,如下所示:

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text"/>

Android控制項的使用規律了,用法基本上都很相似:給控制項定義一個id,再指定控制項的寬度和高度,然後再適當加入一些控制項特有的屬性就差不多了。

所以使用XML來編寫界面其實一點都不難,完全可以不用藉助任何可視化工具來實現。現在重新運行一下程式,EditText就已經在界面上顯示出來了,並且我們是可以在裡面輸入內容的,如圖:

image

做得比較人性化的軟體會在輸入框里顯示一些提示性的文字,然後一旦用戶輸入了任何內容,這些提示性的文字就會消失。這種提示功能在Android里是非常容易實現的,我們甚至不需要做任何的邏輯控制,因為系統已經幫我們都處理好了。修改activity_main.xml,如下所示:

 <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text"
        android:hint="新鮮事兒"/>

image

EditText中顯示了一段提示性文本——“新鮮事兒”,當輸入任何內容時,這段文本就會自動消失。

不過,隨著輸入的內容不斷增多,EditText會被不斷地拉長。這時由於EditText的高度指定的是wrap_content,因此它總能包含住裡面的內容,但是當輸入的內容過多時,界面就會變得非常難看。我們可以使用android:maxLines屬性來解決這個問題,修改activity_main.xml,如下所示:

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text"
        android:hint="新鮮事兒"
        android:maxLines="2"/>

過android:maxLines指定了EditText的最大行數為兩行,這樣當輸入的內容超過兩行時,文本就會向上滾動,而EditText則不會再繼續拉伸。

還可以結合使用EditText與Button來完成一些功能,比如通過點擊按鈕來獲取EditText中輸入的內容。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);

        button.setOnClickListener(this);
    }
    //實現View.OnClickListener介面
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                String inputText = editText.getText().toString();
                Toast.makeText(MainActivity.this,inputText,Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }
}

image

3.2.4 ImageView

ImageView是用於在界面上展示圖片的一個控制項,它可以讓我們的程式界面變得更加豐富多彩。

學習這個控制項需要提前準備好一些圖片,圖片通常都是放在以“drawable”開頭的目錄下的。目前我們的項目中有一個空的drawable目錄,不過由於這個目錄沒有指定具體的解析度,所以一般不使用它來放置圖片。

在res目錄下新建一個drawable-xhdpi目錄,然後將事先準備好的兩張圖片img_1.png和img_2.png複製到該目錄當中。接下來修改activity_main.xml,如下所示:

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/image_view"
        android:src="@drawable/img_1"/>

這裡使用android:src屬性給ImageView指定了一張圖片。由於圖片的寬和高都是未知的,所以將ImageView的寬和高都設定為wrap_content,這樣就保證了不管圖片的尺寸是多少,圖片都可以完整地展示出來。重新運行程式,效果如圖:

image

可以在程式中通過代碼動態地更改ImageView中的圖片,然後修改MainActivity的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText editText;
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView) findViewById(R.id.image_view);

        button.setOnClickListener(this);
    }
    //實現View.OnClickListener介面
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                imageView.setImageResource(R.drawable.img_2);
                break;
            default:
                break;
        }
    }
}

在按鈕的點擊事件里,通過調用ImageView的setImageResource()方法將顯示的圖片改成img_2,現在重新運行程式,然後點擊一下按鈕,就可以看到ImageView中顯示的圖片改變了,如圖:

image

3.2.5 ProgressBar

ProgressBar用於在界面上顯示一個進度條,表示我們的程式正在載入一些數據。它的用法也非常簡單,修改activity_main.xml中的代碼,如下所示:

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/progress_bar"/>

重新運行程式,會看到屏幕中有一個圓形進度條正在旋轉:

image

一個新的知識點:Android控制項的可見屬性

所有的Android控制項都具有這個屬性,可以通過android:visibility進行指定,可選值有3種:visible、invisible和gone。

  • visible表示控制項是可見的,這個值是預設值,不指定android:visibility時,控制項都是可見的。
  • invisible表示控制項不可見,但是它仍然占據著原來的位置和大小,可以理解成控制項變成透明狀態了。
  • gone則表示控制項不僅不可見,而且不再占用任何屏幕空間。

我們還可以通過代碼來設置控制項的可見性,使用的是setVisibility()方法,可以傳入View.VISIBLE、View.INVISIBLE和View.GONE這3種值。

接下來我們就來嘗試實現,點擊一下按鈕讓進度條消失,再點擊一下按鈕讓進度條出現的這種效果。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView) findViewById(R.id.image_view);
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        button.setOnClickListener(this);
    }
    //實現View.OnClickListener介面
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                //imageView.setImageResource(R.drawable.img_2);
                if (progressBar.getVisibility() == View.GONE){
                    progressBar.setVisibility(View.VISIBLE);
                }else{
                    progressBar.setVisibility(View.GONE);
                }
                break;
            default:
                break;
        }
    }
}

在按鈕的點擊事件中,我們通過getVisibility()方法來判斷ProgressBar是否可見,如果可見就將ProgressBar隱藏掉,如果不可見就將ProgressBar顯示出來。

重新運行程式,然後不斷地點擊按鈕,你就會看到進度條會在顯示與隱藏之間來回切換。另外,我們還可以給ProgressBar指定不同的樣式,剛剛是圓形進度條,通過style屬性可以將它指定成水平進度條,修改activity_main.xml中的代碼,如下所示:

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"/>

指定成水平進度條後,我們還可以通過android:max屬性給進度條設置一個最大值,然後在代碼中動態地更改進度條的進度。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        button.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                int progress = progressBar.getProgress();
                progress = progress + 10;
                progressBar.setProgress(progress);
                break;
            default:
                break;
        }
    }
}

每點擊一次按鈕,我們就獲取進度條的當前進度,然後在現有的進度上加10作為更新後的進度。重新運行程式,點擊數次按鈕後,效果如圖:

image

ProgressBar還有幾種其他的樣式,你可以自己去嘗試一下。

3.2.6 AlertDialog

AlertDialog可以在當前的界面彈出一個對話框,這個對話框是置頂於所有界面元素之上的,能夠屏蔽掉其他控制項的交互能力,因此AlertDialog一般都是用於提示一些非常重要的內容或者警告信息。

比如為了防止用戶誤刪重要內容,在刪除前彈出一個確認對話框。修改MainActivity中的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView) findViewById(R.id.image_view);
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        button.setOnClickListener(this);
    }
    //實現View.OnClickListener介面
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                AlertDialog.Builder dialog= new AlertDialog.Builder(MainActivity.this);
                dialog.setTitle("This is Dialog");
                dialog.setMessage("Something important.");
                dialog.setCancelable(false);//可否用Back鍵關閉對話框
                dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                    }
                });
                dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                    }
                });
                dialog.show();
                break;
            default:
                break;
        }
    }
}

首先通過AlertDialog.Builder創建一個AlertDialog的實例,然後可以為這個對話框設置標題、內容、可否用Back鍵關閉對話框等屬性,接下來調用setPositiveButton()方法為對話框設置確定按鈕的點擊事件,調用setNegativeButton()方法設置取消按鈕的點擊事件,最後調用show()方法將對話框顯示出來。

重新運行程式,點擊按鈕後,效果如圖所示:

image

3.2.7 ProgressDialog

ProgressDialog和AlertDialog有點類似,都可以在界面上彈出一個對話框,都能夠屏蔽掉其他控制項的交互能力。不同的是,ProgressDialog會在對話框中顯示一個進度條,一般用於表示當前操作比較耗時,讓用戶耐心地等待。它的用法和AlertDialog也比較相似,修改MainActivity中的代碼,如下所示:

package com.zhouzhou.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
        switch (v.getId()){
            case R.id.button:
                //此處添加邏輯
                ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
                progressDialog.setTitle("This is ProgressDialog");
                progressDialog.setMessage("Loading...");
                progressDialog.setCancelable(true);
                progressDialog.show();

                break;
            default:
                break;
        }
    }
}

可以看到,這裡也是先構建出一個ProgressDialog對象,然後同樣可以設置標題、內容、可否取消等屬性,最後也是通過調用show()方法將ProgressDialog顯示出來。重新運行程式,點擊按鈕後,效果如圖:

image

註意:如果在setCancelable()中傳入了false,表示ProgressDialog是不能通過Back鍵取消掉的,這時你就一定要在代碼中做好控制,當數據載入完成後必須要調用ProgressDialog的dismiss()方法來關閉對話框,否則ProgressDialog將會一直存在。控制項先學習這麼多,閱讀文檔瞭解更多控制項用法。

3.3 詳解4種基本佈局

一個豐富的界面總是要由很多個控制項組成的,那我們如何才能讓各個控制項都有條不紊地擺放在界面上,而不是亂糟糟的呢?這就需要藉助佈局來實現了。

佈局是一種可用於放置很多控制項的容器,它可以按照一定的規律調整內部控制項的位置,從而編寫出精美的界面。當然,佈局的內部除了放置控制項外,也可以放置佈局,通過多層佈局的嵌套,我們就能夠完成一些比較複雜的界面實現,圖:

image

下麵詳細講解下Android中4種最基本的佈局。

先做好準備工作,新建一個UILayoutTest項目,並讓Android Studio自動幫我們創建好活動,活動名和佈局名都使用預設值。

3.3.1 線性佈局

LinearLayout又稱作線性佈局,是一種非常常用的佈局。

正如它的名字所描述的一樣,這個佈局會將它所包含的控制項線上性方向上依次排列。在上一節中學習控制項用法時,所有的控制項就都是放在LinearLayout佈局里的,因此上一節中的控制項也確實是在垂直方向上線性排列的。

既然是線性排列,肯定就不僅只有一個方向,那為什麼上一節中的控制項都是在垂直方向排列的呢?這是由於我們通過android:orientation屬性指定了排列方向是vertical,如果指定的是horizontal,控制項就會在水平方向上排列了。下麵我們通過實戰來體會一下,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="button1"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="button2"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:text="button3"/>

</LinearLayout>

在LinearLayout中添加了3個Button,每個Button的長和寬都是wrap_content,並指定了排列方向是vertical。現在運行一下程式,效果如圖:

image

然後我們修改一下LinearLayout的排列方向,如下所示:

android:orientation="horizontal"

將android:orientation屬性的值改成了horizontal,這就意味著要讓LinearLayout中的控制項在水平方向上依次排列。當然如果不指定android:orientation屬性的值,預設的排列方向就是horizontal。重新運行一下程式,效果如圖:

image

這裡需要註意,如果LinearLayout的排列方向是horizontal,內部的控制項就絕對不能將寬度指定為match_parent,因為這樣的話,單獨一個控制項就會將整個水平方向占滿,其他的控制項就沒有可放置的位置了。同樣的道理,如果LinearLayout的排列方向是vertical,內部的控制項就不能將高度指定為match_parent。

首先來看android:layout_gravity屬性,它和上一節中學到的android:gravity屬性看起來有些相似,這兩個屬性有什麼區別呢?

android:gravity用於指定文字在控制項中的對齊方式,而android:layout_gravity用於指定控制項在佈局中的對齊方式。android:layout_gravity的可選值和android:gravity差不多。

但是需要註意,當LinearLayout的排列方向是horizontal時,只有垂直方向上的對齊方式才會生效,因為此時水平方向上的長度是不固定的,每添加一個控制項,水平方向上的長度都會改變,因而無法指定該方向上的對齊方式。同樣的道理,當LinearLayout的排列方向是vertical時,只有水平方向上的對齊方式才會生效。修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="button1"
        android:layout_gravity="top"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="button2"
        android:layout_gravity="center_vertical"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:text="button3"
        android:layout_gravity="bottom"/>

</LinearLayout>

由於目前LinearLayout的排列方向是horizontal,因此我們只能指定垂直方向上的排列方向,將第一個Button的對齊方式指定為top,第二個Button的對齊方式指定為center_vertical,第三個Button的對齊方式指定為bottom。重新運行程式,效果如圖:

image

LinearLayout中的另一個重要屬性——android:layout_weight。

這個屬性允許我們使用比例的方式來指定控制項的大小,它在手機屏幕的適配性方面可以起到非常重要的作用。比如我們正在編寫一個消息發送界面,需要一個文本編輯框和一個發送按鈕,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <EditText
        android:id="@+id/input_message"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Type something"
        />
    <Button
        android:id="@+id/send"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Send"/>
</LinearLayout>

將EditText和Button的寬度都指定成了0dp,由於我們使用了android:layout_weight屬性,此時控制項的寬度就不應該再由android:layout_width來決定,這裡指定成0dp是一種比較規範的寫法。

另外,dp是Android中用於指定控制項大小、間距等屬性的單位,後面還會經常用到它。然後在EditText和Button里都將android:layout_weight屬性的值指定為1,這表示EditText和Button將在水平方向平分寬度。

然後在EditText和Button里都將android:layout_weight屬性的值指定為1,這表示EditText和Button將在水平方向平分寬度。

image

為什麼將android:layout_weight屬性的值同時指定為1就會平分屏幕寬度呢?

系統會先把LinearLayout下所有控制項指定的layout_weight值相加,得到一個總值,然後每個控制項所占大小的比例就是用該控制項的layout_weight值除以剛纔算出的總值。因此如果想讓EditText占據屏幕寬度的3/5, Button占據屏幕寬度的2/5,只需要將EditText的layout_weight改成3, Button的layout_weight改成2就可以了。

重新運行程式,你會看到如圖的效果:

image

還可以通過指定部分控制項的layout_weight值來實現更好的效果。修改activity_main. xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <EditText
        android:id="@+id/input_message"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Type something"
        />
    <Button
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send"/>

</LinearLayout>

這裡僅指定了EditText的android:layout_weight屬性,並將Button的寬度改回wrap_content。這表示Button的寬度仍然按照wrap_content來計算,而EditText則會占滿屏幕所有的剩餘空間。(

使用layout_weight實現寬度自適配效果,這種方式編寫的界面,不僅在各種屏幕的適配方面會非常好,而且看起來也更加舒服。重新運行程式,效果如圖:

image

3.3.2 相對佈局

RelativeLayout又稱作相對佈局,也是一種非常常用的佈局。和LinearLayout的排列規則不同,RelativeLayout顯得更加隨意一些,它可以通過相對定位的方式讓控制項出現在佈局的任何位置。

也正因為如此,RelativeLayout中的屬性非常多,不過這些屬性都是有規律可循的,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Button1"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:text="Button2"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:layout_centerInParent="true"
        android:text="Button3"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button4"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:text="Button4"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button5"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="Button5"/>
</RelativeLayout>

讓Button 1和父佈局的左上角對齊,Button 2和父佈局的右上角對齊,Button3居中顯示,Button 4和父佈局的左下角對齊,Button 5和父佈局的右下角對齊。雖然android:layout_alignParentLeft、android:layout_alignParentTop、android:layout_alignParentRight、android:layout_alignParentBottom、android:layout_centerInParent這幾個屬性它們的名字已經完全說明瞭它們的作用。

重新運行程式,效果如圖:

image

上面例子中的每個控制項都是相對於父佈局進行定位的,那控制項可不可以相對於控制項進行定位呢?當然是可以的,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:layout_centerInParent="true"
        android:text="Button3"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:layout_above="@+id/button3"
        android:layout_toLeftOf="@+id/button3"
        android:text="Button1"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_above="@+id/button3"
        android:layout_toRightOf="@+id/button3"
        android:text="Button2"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button4"
        android:layout_below="@+id/button3"
        android:layout_toLeftOf="@+id/button3"
        android:text="Button4"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button5"
        android:layout_below="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button5"/>
</RelativeLayout>

android:layout_above屬性可以讓一個控制項位於另一個控制項的上方,需要為這個屬性指定相對控制項id的引用,這裡我們填入了@id/button3,表示讓該控制項位於Button 3的上方。其他的屬性也都是相似的,android:layout_below表示讓一個控制項位於另一個控制項的下方,android:layout_toLeftOf表示讓一個控制項位於另一個控制項的左側,android:layout_toRightOf表示讓一個控制項位於另一個控制項的右側。

註意,當一個控制項去引用另一個控制項的id時,該控制項一定要定義在引用控制項的後面,不然會出現找不到id的情況。重新運行程式,效果如圖:

image

RelativeLayout中還有另外一組相對於控制項進行定位的屬性,android:layout_alignLeft表示讓一個控制項的左邊緣和另一個控制項的左邊緣對齊,android:layout_alignRight表示讓一個控制項的右邊緣和另一個控制項的右邊緣對齊。此外,還有android:layout_alignTop和android:layout_alignBottom,道理都是一樣的。

3.3.3 幀佈局

FrameLayout又稱作幀佈局,它相比於前面兩種佈局就簡單太多了,因此它的應用場景也少了很多。這種佈局沒有方便的定位方式,所有的控制項都會預設擺放在佈局的左上角。讓我們通過例子來看一看吧,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text_view"
        android:text="This is TextView"/>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/image_view"
        android:src="@mipmap/ic_launcher"/>
</FrameLayout>

FrameLayout中只是放置了一個TextView和一個ImageView。需要註意的是,當前項目我們沒有準備任何圖片,所以這裡ImageView直接使用了@mipmap來訪問ic_launcher這張圖,雖說這種用法的場景可能非常少,但我還是要告訴你,這是完全可行的。重新運行程式,效果如圖所示:

image

可以看到,文字和圖片都是位於佈局的左上角。由於ImageView是在TextView之後添加的,因此圖片壓在了文字的上面。除了這種預設效果之外,還可以使用layout_gravity屬性來指定控制項在佈局中的對齊方式,這和LinearLayout中的用法是相似的。修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text_view"
        android:layout_gravity="left"
        android:text="This is TextView"/>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/image_view"
        android:layout_gravity="right"
        android:src="@mipmap/ic_launcher"/>
</FrameLayout>

指定TextView在FrameLayout中居左對齊,指定ImageView在FrameLayout中居右對齊,然後重新運行程式,效果如圖:

image

總體來講,FrameLayout由於定位方式的欠缺,導致它的應用場景也比較少,不過在下一章中介紹碎片的時候我們還是可以用到它的。

3.3.4 百分比佈局

前面介紹的3種佈局都是從Android 1.0版本中就開始支持了,一直沿用到現在,可以說是滿足了絕大多數場景的界面設計需求。

不過你會發現,只有LinearLayout支持使用layout_weight屬性來實現按比例指定控制項大小的功能,其他兩種佈局都不支持。

比如說,如果想用RelativeLayout來實現讓兩個按鈕平分佈局寬度的效果,則是比較困難的。為此,Android引入了一種全新的佈局方式來解決此問題——百分比佈局。在這種佈局中,我們可以不再使用wrap_content、match_parent等方式來指定控制項的大小,而是允許直接指定控制項在佈局中所占的百分比,這樣的話就可以輕鬆實現平分佈局甚至是任意比例分割佈局的效果了。

由於LinearLayout本身已經支持按比例指定控制項的大小了,因此百分比佈局只為FrameLayout和RelativeLayout進行了功能擴展,提供了PercentFrameLayout和PercentRelativeLayout這兩個全新的佈局,下麵我們就來具體學習一下。

不同於前3種佈局,百分比佈局屬於新增佈局,那麼怎麼才能做到讓新增佈局在所有Android版本上都能使用呢?為此,Android團隊將百分比佈局定義在了support庫當中,我們只需要在項目的build.gradle中添加百分比佈局庫的依賴,就能保證百分比佈局在Android所有系統版本上的相容性了。打開app/build.gradle文件,在dependencies閉包中添加如下內容:

dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.0'
    implementation 'androidx.percentlayout:percentlayout:1.0.0'
    testImplementation 'junit:junit:4.13.2'
}

需要註意的是,每當修改了任何gradle文件時,Android Studio都會彈出一個如圖:

image

這個提示告訴我們,gradle文件自上次同步之後又發生了變化,需要再次同步才能使項目正常工作。這裡只需要點擊Sync Now就可以了,然後gradle會開始進行同步,把我們新添加的百分比佈局庫引入到項目當中。接下來修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.percentlayout.widget.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button1"
        android:text="Button1"
        android:layout_gravity="right|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button2"
        android:text="Button2"
        android:layout_gravity="left|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button3"
        android:text="Button3"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>
    <Button
        android:id="@+id/button4"
        android:text="Button4"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%" />
</androidx.percentlayout.widget.PercentFrameLayout>

最外層我們使用了PercentFrameLayout,由於百分比佈局並不是內置在系統SDK當中的,所以需要把完整的包路徑寫出來。然後還必須定義一個app的命名空間,這樣才能使用百分比佈局的自定義屬性。

在PercentFrameLayout中我們定義了4個按鈕,使用app:layout_widthPercent屬性將各按鈕的寬度指定為佈局的50%,使用app:layout_heightPercent屬性將各按鈕的高度指定為佈局的50%。這裡之所以能使用app首碼的屬性就是因為剛纔定義了app的命名空間,當然我們一直能使用android首碼的屬性也是同樣的道理。

不過PercentFrameLayout還是會繼承FrameLayout的特性,即所有的控制項預設都是擺放在佈局的左上角。那麼為了讓這4個按鈕不會重疊,這裡還是藉助了layout_gravity來分別將這4個按鈕放置在佈局的左上、右上、左下、右下4個位置。

現在我們已經可以重新運行程式了,不過如果你使用的是老版本的AndroidStudio,可能會在activity_main.xml中看到一些錯誤提示:

image

這是因為老版本的Android Studio中內置了佈局的檢查機制,認為每一個控制項都應該通過android:layout_width和android:layout_height屬性指定寬高才是合法的。而其實我們是通過app:layout_widthPercent和app:layout_heightPercent屬性來指定寬高的,所以Android Studio沒檢測到。不過這個錯誤提示並不影響程式運行,直接忽視就可以了。

當然最新的Android Studio 2.2版本中已經修複了這個問題,因此你可能並不會看到上述的錯誤提示。現在重新運行程式,效果如圖所示:

image

可以看到,每一個按鈕的寬和高都占據了佈局的50%,這樣我們就輕鬆實現了4個按鈕平分屏幕的效果。另外一個PercentRelativeLayout的用法也是非常相似的,它繼承了RelativeLayout中的所有屬性,並且可以使用app:layout_widthPercent和app:layout_heightPercent來按百分比指定控制項的寬高,最常用的幾種佈局都講解完了,其實Android中還有AbsoluteLayout、TableLayout等佈局,不過使用得實在是太少了。

3.4 系統控制項不夠用?創建自定義控制項

在前面兩節已經學習了Android中的一些常用控制項以及基本佈局的用法,不過當時我們並沒有關註這些控制項和佈局的繼承結構:

image

可以看到,所用的所有控制項都是直接或間接繼承自View的,所用的所有佈局都是直接或間接繼承自ViewGroup的。

View是Android中最基本的一種UI組件,它可以在屏幕上繪製一塊矩形區域,並能響應這塊區域的各種事件,因此,我們使用的各種控制項其實就是在View的基礎之上又添加了各自特有的功能。而ViewGroup則是一種特殊的View,它可以包含很多子View和子ViewGroup,是一個用於放置控制項和佈局的容器。

思考:當系統自帶的控制項並不能滿足我們的需求時,可不可以利用上面的繼承結構來創建自定義控制項呢?

答案是肯定的,下麵我們就來學習一下創建自定義控制項的兩種簡單方法。先將準備工作做好,創建一個UICustomViews項目。

3.4.1 引入佈局

如果你用過iPhone應該會知道,幾乎每一個iPhone應用的界面頂部都會有一個標題欄,標題欄上會有一到兩個按鈕可用於返回或其他操作(iPhone沒有實體返回鍵)。

現在很多Android程式也都喜歡模仿iPhone的風格,在界面的頂部放置一個標題欄。雖然Android系統已經給每個活動提供了標題欄功能,但這裡我們決定先不使用它,而是創建一個自定義的標題欄。

一般我們的程式中可能有很多個活動都需要標題欄,如果在每個活動的佈局中都編寫一遍同樣的標題欄代碼,明顯就會導致代碼的大量重覆。這個時候我們就可以使用引入佈局的方式來解決這個問題,新建一個佈局title.xml,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/title_bg">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/title_back"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/title_text"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/title_edit"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff"/>
</LinearLayout>

可以看到,在LinearLayout中分別加入了兩個Button和一個TextView,左邊的Button可用於返回,右邊的Button可用於編輯,中間的TextView則可以顯示一段標題文本。

android:background用於為佈局或控制項指定一個背景,可以使用顏色或圖片來進行填充,這裡我提前準備好了3張圖片——title_bg.png、back_bg.png和edit_bg.png,分別用於作為標題欄、返回按鈕和編輯按鈕的背景。

另外,在兩個Button中我們都使用了android:layout_margin這個屬性,它可以指定控制項在上下左右方向上偏移的距離,當然也可以使用android:layout_marginLeft或android:layout_marginTop等屬性來單獨指定控制項在某個方向上偏移的距離。現在標題欄佈局已經編寫完成了,剩下的就是如何在程式中使用這個標題欄了,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include layout="@layout/title"/>
</LinearLayout>

只需要通過一行include語句將標題欄佈局引入進來就可以了。最後別忘了在MainActivity中將系統自帶的標題欄隱藏掉,代碼如下所示:

package com.zhouzhou.uicustomviews;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
    }
}

這裡調用了getSupportActionBar()方法來獲得ActionBar的實例,然後再調用ActionBar的hide()方法將標題欄隱藏起來。效果如圖:

image

使用這種方式,不管有多少佈局需要添加標題欄,只需一行include語句就可以了。

3.4.2 創建自定義控制項

引入佈局的技巧確實解決了重覆編寫佈局代碼的問題,但是如果佈局中有一些控制項要求能夠響應事件,我們還是需要在每個活動中為這些控制項單獨編寫一次事件註冊的代碼。

比如說標題欄中的返回按鈕,其實不管是在哪一個活動中,這個按鈕的功能都是相同的,即銷毀當前活動。而如果在每一個活動中都需要重新註冊一遍返回按鈕的點擊事件,無疑會增加很多重覆代碼,這種情況最好是使用自定義控制項的方式來解決。

新建TitleLayout繼承自LinearLayout,讓它成為我們自定義的標題欄控制項,代碼如下所示:

package com.zhouzhou.uicustomviews;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

public class TitleLayout extends LinearLayout{

    public TitleLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title,this);
    }
}

首先重寫了LinearLayout中帶有兩個參數的構造函數,在佈局中引入TitleLayout控制項就會調用這個構造函數。然後在構造函數中需要對標題欄佈局進行動態載入,這就要藉助LayoutInflater來實現了。通過LayoutInflater的from()方法可以構建出一個LayoutInflater對象,然後調用inflate()方法就可以動態載入一個佈局文件,inflate()方法接收兩個參數,第一個參數是要載入的佈局文件的id,這裡我們傳入R.layout.title,第二個參數是給載入好的佈局再添加一個父佈局,這裡我們想要指定為TitleLayout,於是直接傳入this。

現在自定義控制項已經創建好了,然後我們需要在佈局文件中添加這個自定義控制項,修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.zhouzhou.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

添加自定義控制項和添加普通控制項的方式基本是一樣的,只不過在添加自定義控制項的時候,我們需要指明控制項的完整類名,包名在這裡是不可以省略的。

重新運行程式,發現此時效果和使用引入佈局方式的效果是一樣的。嘗試為標題欄中的按鈕註冊點擊事件,修改TitleLayout中的代碼,如下所示:

package com.zhouzhou.uicustomviews;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;

public class TitleLayout extends LinearLayout{

    public TitleLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title,this);
        Button titleBack = (Button) findViewById(R.id.title_back);
        Button titleEdit = (Button) findViewById(R.id.title_edit);
        titleBack.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                ((Activity)getContext()).finish();
            }
        });
        titleEdit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getContext(),"You clicked Edit button", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

首先還是通過findViewById()方法得到按鈕的實例,然後分別調用setOnClickListener()方法給兩個按鈕註冊了點擊事件,當點擊返回按鈕時銷毀掉當前的活動,當點擊編輯按鈕時彈出一段文本。重新運行程式,點擊一下編輯按鈕,效果如圖:

image

這樣,每當我們在一個佈局中引入TitleLayout時,返回按鈕和編輯按鈕的點擊事件就已經自動實現好了,這就省去了很多編寫重覆代碼的工作。

3.5 最常用和最難用的控制項——ListView

ListView絕對可以稱得上是Android中最常用的控制項之一,幾乎所有的應用程式都會用到它。

由於手機屏幕空間都比較有限,能夠一次性在屏幕上顯示的內容並不多,當我們的程式中有大量的數據需要展示的時候,就可以藉助ListView來實現。

ListView允許用戶通過手指上下滑動的方式將屏幕外的數據滾動到屏幕內,同時屏幕上原有的數據則會滾動出屏幕。比如查看QQ聊天記錄,翻閱微博最新消息,等等。不過比起前面介紹的幾種控制項,ListView的用法也相對複雜了很多。

3.5.1 ListView的簡單用法

首先新建一個ListViewTest項目,並讓Android Studio自動幫我們創建好活動。然後修改activity_main.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_view"/>
</LinearLayout>

在佈局中加入ListView控制項還算非常簡單,先為ListView指定一個id,然後將寬度和高度都設置為match_parent,這樣ListView也就占滿了整個佈局的空間。接下來修改MainActivity中的代碼,如下所示:

package com.zhouzhou.listviewtest;

import an

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、window 概念 視窗(window)是處理無限流的核心。視窗將流分割成有限大小的“桶”,我們可以在桶上應用計算。本文檔重點介紹如何在Flink中執行視窗操作,以及程式員如何從其提供的功能中獲得最大的好處。 一個有視窗的Flink程式的一般結構如下所示。第一個片段指的是鍵控流,而第二個片段指的 ...
  • 本文力求以簡單易懂的語言描述出資料庫發展史,儘量避免出現複雜的概念介紹。資料庫演進史如圖1所示: 圖1 資料庫演進 一、穿孔紙帶和文件系統 在現代意義的資料庫出現之前(20世紀60年代),人們通過人工和文件系統的方式來存儲、管理數據。在人工管理時期,人們常使用穿孔紙帶來管理數據(圖2),雖然穿孔紙帶 ...
  • 19c單實例配置GoldenGate 併進行用戶數據同步測試 當前數據使用的是經典模式配置GoldenGate 進行數據同步,關於容器資料庫配置GoldenGate 請參閱: Configuring Oracle GoldenGate in a Multitenant Container Datab ...
  • 離線CDH集群自動化部署工具 離線CDH集群安裝與部署的自動化腳本工具,簡單支持「離線一鍵裝機」。 腳本將對系統配置做出一定修改,使用前請務必確認當前伺服器無其他人員、任務使用,以免造成不必要的麻煩,建議提前使用測試伺服器或虛擬機測試體驗。 一、Features 已實現的自動化功能(僅支持Redha ...
  • 一、引言 需求描述:現實工作中,有一些很特別的需求:在一個彙總表中,需要顯示明細數據。因為是在彙總表中,所以明細數據只能顯示在某一列中,這個列,就是多行數據合併為一行之後的結果。 案例描述:比如,在物料凈需求表中,需求量扣減庫存量、在途量等等之後,結果為剩餘量,剩餘量為負則需要採購。此時,不管剩餘量 ...
  • 5月中國資料庫排行榜已出爐!各大資料庫廠商表現如何?排名有何變化?一起來解讀吧! ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...