第一行代碼學習筆記第三章——UI開發的點點滴滴

知識點目錄

知識點回顧

3.1 如何編寫程序界面

Android開發中編寫界面的方法主要有如下兩種:

  • 可視化編輯器

    優點:允許拖放控件來編寫佈局,同時可以在視圖上修改控件的屬性

    缺點:不利於瞭解界面背後的原理,屏幕適配性不好

  • XML代碼

    這是用的最多的方式。不僅能夠實現高度複雜的控件,還能分析和修改當前現有界面。

3.2 常用控件的使用方法

Android中提供了大量的UI控件,下面我們學習幾種比較常用的控件。

3.2.1 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" />

屬性說明:

  • android:id 給當前控件定義一個唯一標識符。

  • android:layout_width和android:layout_height指定控件的寬度和高度,Android中所有的控件都有這兩個屬性,值有match_parent、fill_parent和wrap_content。其中:match_parent和fill_parent意義相同,表示讓當前控件的大小和父佈局的大小一樣;wrap_content表示讓當前控件的大小能夠剛好包含住裏面的內容。

  • android:gravity 指定文字的對齊方式,可選值有top、bottom、left、right、center等。可以用“|”來同時指定多個值。

  • android:textSize 指定文字的大小。Android中字體大小使用sp作爲單位。

  • android:textColor 指定文字顏色。

  • android:text 指定TextView中顯示的文本內容。

這裏簡單的解釋了TextView中的幾個屬性,其它屬性等用到的時候自行查閱。

3.2.2 Button

主要作用:

顯示一個按鈕,與用戶進行交互。

Button可配置的屬性和TextView差不多。

使用方法:

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

屬性說明:

其它屬性跟上面說的TextView一樣,這裏說一個特殊的。

  • android:textAllCaps 如果不加這個屬性,那麼上面設置的文字Button最終顯示的卻是“BUTTON”,這是因爲系統會對Button中的所有英文字母自動進行大寫轉換,如果不想要這個效果,可以將android:textAllCaps設置爲false。

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 v) {
                      // 在此處添加邏輯
                  }
              });	
          }
      }
    
  • 方式二(實現接口):

      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:
                      // 在此處添加邏輯
                      break;
                  default:
                      break;
              }
          }
      }
    
3.2.3 EditText

主要作用:

用於和用戶進行交互,用戶可以在控件裏面輸入和編輯內容,並可以在程序中對這些內容進行處理。

使用方法:

<EditText
    android:id="@+id/edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Type something here"
    android:maxLines="2"/>

屬性說明:

  • android:hint 指定一段提示性文本,用戶輸入後,文本自動消失。

  • android:maxLines 指定EditText的最大行數,當輸入的內容超過行數後,文本就會向上滾動。

獲取輸入框文本:

EditText editText = (EditText) findViewById(R.id.edit_text);
String inputText = editText.getText().toString();
3.2.4 ImageView

主要作用:

用於在界面上展示圖片。圖片根據分辨率的不同放在不同的drawable或mipmap目錄下。

使用方法:

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

屬性說明:

  • android:src 給ImageView指定一張圖片。

代碼中指定圖片:

ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageResource(R.mipmap.ic_launcher_round);
3.2.5 ProgressBar

主要作用:

在界面上顯示一個進度條。

使用方法:

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

屬性說明:

  • android:visibility 控制控件是否可見。 visible表示控件可見,默認值。invisible表示控件不可見,但仍佔據原來的位置,相當於控件透明。gone表示控件不可見,且不佔據原來的位置。

  • style 指定控件的形狀。

  • android:max 給進度條設置一個最大值。

代碼中控制控件是否可見

ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
if (progressBar.getVisibility() == View.GONE) {
    progressBar.setVisibility(View.VISIBLE);
} else {
    progressBar.setVisibility(View.GONE);
}
3.2.6 AlertDialog

主要作用:

用於提示一些非常重要的內容或者警告信息。比如爲了防止用戶誤刪重要內容,在刪除前彈出一個確認對話框。

使用方法:

    AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
    dialog.setTitle("This is Dialog");
    dialog.setMessage("Something improtant");
    dialog.setCancelable(false);
    dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            
        }
    });
    dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            
        }
    });
	dialog.show();

使用說明:

  • AlertDialog.Builder創建一個AlertDialog實例

  • setTitle 設置標題

  • setMessage 設置內容

  • setCancelable 是否用Back鍵關閉對話框

  • setPositiveButton() 設置確認按鈕的點擊事件

  • setNegativeButton() 設置取消按鈕的點擊事件

  • show() 將dialog顯示出來

3.2.7 ProgressDialog

主要作用:

跟AlertDialog類似,都能夠屏蔽掉其他控件的交互能力。只是ProgressDialog會在對話框中顯示一個進度條,一般用於表示當前操作比較耗時,讓用戶耐心地等待。

使用方法:

    ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
    progressDialog.setTitle("This is ProgressDialog");
    progressDialog.setMessage("Loding...");
    progressDialog.setCancelable(true);
    progressDialog.show();

使用說明:

  • new ProgressDialog 創建一個ProgressDialog對象

  • setTitle 設置標題

  • setMessage 設置內容

  • setCancelable 是否用Back鍵關閉對話框

  • show() 將dialog顯示出來

備註:

如果在setCancelable()中傳入了false,表示ProgressDialog不能通過Back鍵取消掉,這時需要在代碼中做好控制,當數據加載完成後必須調用ProgressDialog的dismiss()方法來關閉對話框,否則ProgressDialog將會一直存在。

3.3 詳解4中基本佈局

佈局是一種可以放置多個控件或者佈局的容器,可以按照一定的規律調整內部控件或佈局的位置,從而來寫出精美的界面。

3.3.1 線性佈局

LinearLayout稱作線性佈局,該佈局會將它所包含的控件在線性方向(水平或垂直方向)上依次排列。

基本使用:

<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:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="Button 2"/>

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="Button 3"/>

	<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>

屬性說明:

  • android:orientation 指定排列方向,可以是vertical或horizontal。默認的排列方向是horizontal

  • android:layout_weight 使用比例的方式來指定控件的大小

備註:

  • 如果LinearLayout的排列方向是horizontal,內部的控件寬度就不能指定爲match_parent,因爲這樣的話,單獨的一個控件就會將整個水平方向佔滿,其他的控件就沒有可放的位置。同理,如果LinearLayout的排列方向是vertical,內部控件的高度就不能指定爲match_parent。

  • android:layout_gravity用於指定控件在佈局中的對齊方式,而android:gravity用於指定文字在控件中的對齊方式。

  • LinearLayout的排列方式是horizontal時,只有在垂直方向上的對齊方式纔會生效,因爲此時水平方向上的長度是不固定的,每添加一個控件,水平方向上的長度都會改變,因此無法確定該方向上的對齊方式。同理,當LinearLayout的排列方向是vertical時,只有水平方向上的對齊方式纔會生效。

3.3.2 相對佈局

RelativeLayout稱作相對佈局。通過相對定位的方式讓控件出現在佈局的任何位置。

基本使用:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Button 2"/>

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Button 3"/>

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        android:text="Button 4"/>

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:text="Button 5"/>

    <Button
        android:id="@+id/button6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 6"/>

    <Button
        android:id="@+id/button7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 7"/>

    <Button
        android:id="@+id/button8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 8"/>

    <Button
        android:id="@+id/button9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 9"/>

</RelativeLayout>

效果圖:

屬性說明:

  • android:layout_alignParentLeft 與父佈局的左邊對齊

  • android:layout_alignParentRight 與父佈局的右邊對齊

  • android:layout_alignParentTop 與父佈局的上邊對齊

  • android:layout_alignParentBottom 與父佈局的下邊對齊

  • android:layout_centerInParent 位於父佈局中心

上面的屬性主要用在控件相對於父佈局進行定位的屬性。但控件也可以相對於其他控件進行定位!

  • android:layout_above 位於相對控件的上方

  • android:layout_below 位於相對控件的下方

  • android:layout_toLeftOf 位於相對控件的左側

  • android:layout_toRightOf 位於相對控件的右側

**備註:**當一個控件去引用另一個控件id的時候,該控件一定要定義在引用控件的後面,不然會出現找不到id的情況。

3.3.3 幀佈局

FrameLayout稱作幀佈局。它裏面所有的控件都會默認擺放在佈局的左上角。

基本使用:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:text="This is TextView"/>

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:src="@mipmap/ic_launcher"/>

</FrameLayout>

屬性說明:

  • android:layout_gravity 指定控件在佈局中的對齊方式。
3.3.4 百分比佈局

在前面介紹的LinearLayout、RelativeLayout和FrameLayout三種佈局,只有LinearLayout支持使用layout_weight屬性來實現按比例指定控件大小的功能。爲此,Android引入了百分比佈局。我們不再需要使用wrap_content、match_parent等方式來指定控件的大小,而是直接指定控件在佈局中所佔的百分比,這樣就可以實現任意比例分割佈局的效果。

由於LinearLayout本身支持按比例指定控件的大小,因此百分比佈局只爲RelativeLayout和FrameLayout進行了功能擴展,提供了PercentFrameLayout和PercentRelativeLayout這兩個佈局。

Android開發人員將百分比佈局定義在了support庫中,因此我們需要在項目的build.gradle中添加百分比佈局庫的依賴,就能保證百分比佈局在Android所以系統版本上的兼容性。

打開app/build.gradle文件,在dependencies閉包中添加如下內容:

dependencies {
    implementation 'com.android.support:percent:25.3.1'
}

基本使用:

<android.support.percent.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="Button 1"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button2"
        android:text="Button 2"
        android:layout_gravity="right|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button3"
        android:text="Button 3"
        android:layout_gravity="left|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button4"
        android:text="Button 4"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

</android.support.percent.PercentFrameLayout>

效果圖:

屬性說明:

  • android.support.percent.PercentFrameLayout 由於百分比佈局並不是內置在系統SDK當中,所以需要把完整的包路徑寫出來

  • xmlns:app 定義一個app的命名空間,這樣才能使用百分比佈局的自定義屬性

  • app:layout_widthPercent 指定控件的寬度在佈局中所佔的百分比

  • app:layout_heightPercent 指定控件的高度在佈局中所佔的百分比

  • PercentFrameLayout繼承了FrameLayout的特性,所以我們需要通過android:layout_gravity屬性來指定放置佈局的位置。

PercentRelativeLayout的用法同PercentFrameLayout很類似。它繼承了RelativeLayout的所有特性。可按照PercentFrameLayout自行嘗試。

3.4 創建自定義控件

前面學習了Android中的常用控件和佈局,順便梳理下控件和佈局之間的繼承關係:

從上面的圖中可得出如下結論:

  • 所有控件都是直接或間接繼承自View

  • 所有的佈局都是直接或間接繼承自ViewGroup

  • View是Android中最基本的一種UI控件,它可以在屏幕上繪製一塊矩形區域,並能響應這塊區域的各種事件

  • ViewGroup是一種特殊的View,它可以包含很多子View和子ViewGroup,是一個用於放置控件和佈局的容器

3.4.1 引入佈局

在平時的項目開發中,很多活動可能都需要相同樣式的標題欄,如果在每個活動中都編寫一遍同樣的標題欄代碼,就會導致代碼的大量重複,這時候我們就可以用引入佈局的方式來解決這個問題。

新建佈局:

新建一個title.xml,代碼如下所示:

<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:id="@+id/title_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff"/>

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp"/>

    <Button
        android:id="@+id/title_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff"/>

</LinearLayout>

屬性說明:

  • android:background 用於給控件或佈局指定一個背景

  • android:layout_margin 指定控件在上下左右方向上偏移的距離,單位爲dp

使用標題欄:

<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語句將標題欄佈局引入進來。

隱藏系統自帶的標題欄

在顯示我們通過引入佈局寫的標題欄前,我們需要隱藏掉系統自帶的標題欄:

ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
    actionBar.hide();
}

通過調用getSupportActionBar()獲得ActionBar的實例,然後調用ActionBar的hide()方法將標題欄隱藏。

效果圖:

3.4.2 創建自定義控件

引入佈局很好的解決了重複編寫佈局代碼的問題,但是如果佈局中有一些控件要求能夠響應事件,那麼我們就需要在每個活動中單獨編寫一次事件註冊的代碼。比如標題欄中的返回按鈕,其實不管在哪個活動中,這個按鈕的功能都是銷燬當前活動。如果每一個活動中都需要重新註冊一遍返回按鈕的點擊事件,這樣就會增加很多重複代碼,此時我們就應使用自定義控件的方式來解決。

自定義控件

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 v) {
                ((Activity) getContext()).finish();
            }
        });
        titleEdit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "You click Edit button", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
  • 重寫了LinearLayout帶有兩個參數的構造函數,在佈局中引入TitleLayout控件就會調用這個構造函數

  • 使用LayoutInflater對標題欄佈局進行動態加載,LayoutInflater的from()方法構建出一個LayoutInflater對象,然後調用inflate()方法動態加載一個佈局。

  • inflate()方法第一個參數是要加載佈局文件的id,第二個參數是給加載好的佈局添加一個父佈局。

  • 通過findViewById()找到佈局文件中的控件,分別爲各個按鈕註冊點擊事件。

在佈局中使用自定義控件

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

備註:
在使用自定義控件的時候,我們需要指定控件的完整類名。

效果圖:

3.5 ListView

ListView允許用戶通過手指上下滑動的方式將屏幕外的數據滾動到屏幕內,同時屏幕上原有的數據則會滾動出屏幕。

3.5.1 ListView的簡單用法

在activity_main.xml中的使用:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

在MainActivity中的代碼:

public class MainActivity extends AppCompatActivity {

    private String[] data = {"Apple","Banana","Orange","Watermelon","Pear",
            "Grape","Pineapple","Strawberry","Cherry","Mango",
            "Apple","Banana","Orange","Watermelon","Pear",
            "Grape","Pineapple","Strawberry","Cherry","Mango",};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView listView = (ListView) findViewById(R.id.list_view);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,
                android.R.layout.simple_list_item_1,data);
        listView.setAdapter(adapter);
    }
}
  • data 是ListView中要顯示的數據,可以自定義,也可以從後臺獲取

  • ArrayAdapter 數據無法直接傳給ListView,需要藉助適配器來完成。而ArrayAdapter是Android中提供的適配器實現類,可以通過泛型來指定要適配的數據類型。

  • ArrayAdapter 構造函數參數一:當前上下文;參數二:ListView子項佈局的id;參數三:要適配的數據。

  • android.R.layout.simple_list_item_1 是一個Android內置的佈局,裏面只有一個TextView,可用於簡單地顯示一段文本。

  • listView.setAdapter() 通過該方法將構建好的適配器對象傳遞進去,這樣ListView和數據之間就建立了關聯。

效果圖:

3.5.2 定製ListView的界面

ListView的子項佈局正常情況下不止一段文本,下面我們就在上面每個子項佈局的水果旁邊加上一個圖樣。

定義實體類

public class Fruit {
    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }
}

其中:name表示水果的名字,imageId表示水果對應圖片的資源id。

自定義ListView子項佈局

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>

</LinearLayout>

自定義適配器

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;

    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
        resourceId = resource;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent,false);
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }
}
  • FruitAdapter 重寫父類個構造函數,將上下文、ListView子項佈局id和數據傳遞進來

  • 重寫getView()方法。每個子項滾動到屏幕內時都會調用這個方法。

  • getItem()方法得到當前項的Fruit實例

  • LayoutInflater 爲子項加載我們傳入的佈局

  • inflate()方法的參數三要傳入false,表示只讓我們在父佈局中聲明的layout生效,但不會爲這個View添加父佈局,因爲View一旦有了父佈局,就不能將它添加到ListView中

  • 調用View的findViewById()方法分別獲取到ImageView和TextView的實例,然後分別調用setImageResource()和setText()來設置顯示圖片和文字

在MainActivity中的代碼:

public class MainActivity extends AppCompatActivity {

    private List<Fruit> mFruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        ListView listView = (ListView) findViewById(R.id.list_view);
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, mFruitList);
        listView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            mFruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            mFruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            mFruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            mFruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            mFruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            mFruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            mFruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            mFruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            mFruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            mFruitList.add(mango);
        }
    }
}

效果圖

3.5.3 提升ListView的運行效率

上面實例中的ListView運行效率很低,主要原因有如下兩點:

  • FruitAdapter的getView方法中,每次都將佈局重新加載一遍

  • FruitAdapter的getView方法中,每次都會調用View的findViewById()方法去獲取控件的實例

針對上面的兩種情況,我們可以使用convertView和ViewHolder去優化:

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;

    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
        resourceId = resource;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder); //將viewHolder存儲在View中
        } else {
            view = convertView;
            viewHolder = ((ViewHolder) view.getTag());
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
    }
}
  • 在getView()方法中,判斷convertView是否爲null,如果爲null,則使用LayoutInflater去加載佈局;如果不爲null,則直接對convertView重用

  • 新建一個內部類ViewHolder,然後分別使用setTag()和getTag()方法去存儲和取出

3.5.4 ListView的點擊事件

使用setOnItemClickListener()方法爲ListView註冊一個監聽器:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initFruits();
    ListView listView = (ListView) findViewById(R.id.list_view);
    FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, mFruitList);
    listView.setAdapter(adapter);
	//註冊監聽器
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
        }
    });
}

當用戶點擊了ListView中的任何一個子項時,就會回調onItemClick()方法,這個方法中我們可以通過position參數來判斷出用戶點擊的是哪一個子項。

3.6 RecyclerView

ListView只能實現數據縱向滾動效果,無法實現橫向滾動。因此Android提供了一個增強版的ListView——RecyclerView。

3.6.1 RecyclerVeiw的基本用法

1. 添加依賴

RecyclerVeiw屬於新增控件,Android團隊將其定義在了support庫中,因此我們需要在項目的build.gradle中添加相應的依賴庫。

dependencies {
    implementation 'com.android.support:recyclerview-v7:24.2.1'
}

2. 佈局中使用

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

由於RecyclerView並不是內置在系統SDK當中的,所以需要把完整的包路徑寫出來。

3. 新建適配器

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

    public FruitAdapter(List<Fruit> fruitList) {
        mFruitList = fruitList;
    }

    //創建ViewHolder實例
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    //對RecyclerView子項的數據進行賦值
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    //RecyclerView有多少子項
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
        public ViewHolder(View itemView) {
            super(itemView);
            fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));
            fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));
        }
    }
}

4. 真正使用RecyclerView

public class MainActivity extends AppCompatActivity {

    private List<Fruit> mFruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(mFruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            mFruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            mFruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            mFruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            mFruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            mFruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            mFruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            mFruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            mFruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            mFruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            mFruitList.add(mango);
        }
    }
}

LayoutManager用於指定RecyclerView的佈局方式,這裏使用的LinearLayoutManager是線性佈局的意思,可以實現和ListView類似的效果。

5. RecyclerView效果圖

3.6.2 實現橫向滾動和瀑布流佈局

橫向滾動

1. 修改fruit_item.xml文件

因爲要橫向滾動,所以需要對上面的fruit_item.xml文件進行修改:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="100dp"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

</LinearLayout>

2. 設置佈局橫向滾動

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
		//設置RecyclerView的方向
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(mFruitList);
        recyclerView.setAdapter(adapter);
    }

RecyclerView的佈局排列是由LayoutManager去控制,LayoutManager中制定了一套可擴展的佈局排列接口,子類只要按照接口的規範來實現,就能定製出各種不同排列方式的佈局。

3. 效果圖

瀑布流佈局

除了LinearLayoutManager之外,RecyclerView還提供了GridLayoutManager和StaggerdGridLayoutManager這兩種內置的佈局排列方式。其中,GridLayoutManager可以用於實現網格佈局,StaggerdGridLayoutManager可以用於實現瀑布流佈局。

1. 修改fruit_item.xml文件

因爲要實現瀑布流效果,所以需要對上面的fruit_item.xml文件進行修改:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp"/>

</LinearLayout>

2. 使用StaggerdGridLayoutManager

public class MainActivity extends AppCompatActivity {

    private List<Fruit> mFruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(mFruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
            mFruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
            mFruitList.add(banana);
            Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
            mFruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
            mFruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
            mFruitList.add(pear);
            Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
            mFruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
            mFruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
            mFruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
            mFruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);
            mFruitList.add(mango);
        }
    }
    private String getRandomLengthName(String name) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(name);
        }
        return builder.toString();
    }
}

StaggeredGridLayoutManager的構造函數接受兩個參數,參數一:用於指定佈局的列數,傳入3表示會把佈局分爲3列;參數二用於指定佈局的排列方向。

3. 效果圖

3.6.3 RecyclerView的點擊事件

RecyclerView並沒有提供類似於setOnItemClickListener()這樣的註冊監聽器方法,而是需要我們自己給子項具體的View去註冊點擊事件。

其實ListView的setOnItemClickListener()方法註冊的是子項的點擊事件,如果想點擊子項裏面具體的某一個按鈕,ListView實現起來就有點複雜。爲此,RecyclerView乾脆直接摒棄了子項點擊事件的監聽器,所有的點擊事件都由具體的View去註冊。

註冊監聽

RecyclerView的點擊事件是在Adapter中實現的,修改FruitAdapter文件:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

    public FruitAdapter(List<Fruit> fruitList) {
        mFruitList = fruitList;
    }

    //創建ViewHolder實例
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        //給子項最外層佈局註冊點擊事件
        holder.fruitView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked view " + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        //給子項中的ImageView註冊點擊事件
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked image " + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }

    //對RecyclerView子項的數據進行賦值
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    //RecyclerView有多少子項
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
        View fruitView; //子項最外層佈局實例
        public ViewHolder(View itemView) {
            super(itemView);
            fruitView = itemView;
            fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));
            fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));
        }
    }
}

實現步驟:

  • 在ViewHolder中添加fruitView變量保存子項最外層佈局的實例

  • 在onCreateViewHolder中註冊相關控件的點擊事件

效果圖

3.7 編寫界面的最佳實踐

3.7.1 製作Nine-Patch圖片

在Android sdk目錄下有一個tools文件夾,在這個文件夾中找到draw9patch.bat文件,我們使用他來製作Nine-Patch

要打開這個文件,必須先將JDK的bin目錄配置到環境變量中。例如使用的是AndroidStudio內置的sdk,要配置的路徑就是AndroidStudio安裝目錄/jre/bin

雙擊打開draw9patch.bat文件,在導航欄點擊File——>Open 9-patch將需要製作的圖片加載進來,如下圖所示:

可以在圖片的四個邊框繪製一個個的小黑點。上邊框和左邊框繪製的部分表示當圖片需要拉伸時就拉伸黑點標記的區域;下邊框和右邊框繪製的部分表示內容會被放置的區域。

3.7.2 編寫精美的聊天界面

略略略。。。

非常感謝您的耐心閱讀,希望我的文章對您有幫助。歡迎點評、轉發或分享給您的朋友或技術羣。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章