1. 程式人生 > Android入門教學 >Android 網格檢視 GridView

Android 網格檢視 GridView

1. GridView 的特性

GridView 在 Android App 中運用非常廣泛,比如我們手機的系統相簿將我們的照片及照片名稱按照網格的樣式排列起來,並且可以上下滾動,這種效果非常適合用 GridView 實現。
為了實現 MVC 模式,更方便的管理資料與 UI,GridView 通過 Adapter 完成資料的填充,Adapter 的使用幾乎和 ListView 一樣,另外系統提供了幾種簡單的 Adapter,具體可以參考 23 節和 24 節的內容。

2. GridView 的基本用法

GridView 和 上一節所學的 ListView 極其相似,主要還是從屬性、API 及事件監聽器三個方面來介紹基本用法。

2.1 GridView 的常用屬性

  • android:columnWidth:
    設定網格的列寬
  • android:gravity:
    網格內各個 item 的對齊方式
  • android:horizontalSpacing:
    網格的各個 item 在水平方向上的間距
  • android:verticalSpacing:
    網格的各個 item 在垂直方向上的間距
  • android:numColumns:
    設定列數,可以直接設定距離也可以設定成“auto_fit”讓系統自適應
  • android:stretchMode:
    設定網格拉伸模式,可選值如下:
    • none: 不拉伸
    • spacingWidth: 將多餘空間分攤給網格的間隔空隙
    • columnWidth: 將多餘空間分攤給網格的各個列
    • spacingWidthUniform: 將多餘空間分攤給網格的各個列及其間隔空隙

2.2 GridView 的常用 API

  • smoothScrollByOffset:
    平滑滾動一個相對距離
  • smoothScrollToPosition:
    平滑滾動到某個位置

2.3 GridView 的事件監聽器

  • setOnItemClickListener:
    設定 GridView 的 item 點選事件回撥,此介面與 ListView 完全一樣。

3. GridView 的使用示例

為了和 ListView 以及 Adapter 兩節的內容作對比,本節依舊實現“水果列表”的例子,只不過本節會把單維列表改成二維的網格樣式,程式碼可基於上一節直接修改。

3.1 佈局檔案編寫

首先修改佈局檔案,將 ListView 替換成 GridView 並新增一些 GridView 特有的屬性,如下:

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnWidth="110dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center" />

3.2 GridView 介面卡

以上屬性都在 25.2.1 小節有描述,也比較好理解,接著修改 MyAdapter 類,它是繼承自 BaseAdapter 實現的自定義介面卡,因為二維列表更節省空間,上一節的水果數目已經沒法佔滿一屏,這樣會導致列表不能滑動,不利於體驗 GridView 的效果。為此有兩種解決方法:一個是我們可以手動擴充套件我們的列表陣列,增加一些水果名稱和圖片;第二種方法是直接修改介面卡,不斷地迴圈從之前的列表中取水果資料。為了讓大家更好的理解介面卡的原理,我們採用第二種方法來擴充套件列表,我們需要修改兩個回撥方法:getCountgetView

  • getCount 表示列表的總 item 數,我們直接將水果列表長度乘以10:
     @Override
      public int getCount() {
          return mName.length * 10;
      }
    
  • getView 中我們完成 View 和資料的繫結,我們需要迴圈取數,所以只需要將 item 的位置對陣列大小取模即可:
    @Override
      public View getView(int position, View convertView, ViewGroup parent) {
          convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
          TextView name = convertView.findViewById(R.id.textView);
          ImageView image = convertView.findViewById(R.id.imageView);
    
          // item 的位置對陣列長度取模,實現迴圈取值
          name.setText(mName[position % mName.length]);    
          image.setImageResource(mResId[position % mResId.length]);
    
          return convertView;
      }
    

完整的 MyAdapter 程式碼如下:

package com.emercy.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;


public class MyAdapter extends BaseAdapter {

    private Context mContext;
    private String[] mName;
    private int[] mResId;

    public MyAdapter(Context context) {
        mContext = context;
    }

    public void setData(String[] name, int[] resId) {
        mName = name;
        mResId = resId;
    }


    @Override
    public int getCount() {
        return mName.length * 10;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
        TextView name = convertView.findViewById(R.id.textView);
        ImageView image = convertView.findViewById(R.id.imageView);
        name.setText(mName[position % mName.length]);
        image.setImageResource(mResId[position % mResId.length]);
        return convertView;
    }
}

3.3 主 Activity 邏輯

Activity 的邏輯其實和 ListView 中的例子完全一樣,只需要將所有的 ListView 型別改成 GridView 即可。這裡體現了 MVC 設計思路的靈活性,我們想要替換一個樣式其實只需要修改佈局檔案,主邏輯和資料層完全不需要修改,這就是前面所說的 UI 和資料解耦的強大優勢。MainActivity 程式碼如下:

package com.emercy.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.Toast;

public class MainActivity extends Activity {

    GridView mGridView;
    String[] mDataName = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
    int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
            R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGridView = findViewById(R.id.gridview);

        MyAdapter adapter = new MyAdapter(this);
        adapter.setData(mDataName, mDataImage);
        mGridView.setAdapter(adapter);
        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(getApplicationContext(), mDataName[i % mDataName.length], Toast.LENGTH_LONG).show();
            }
        });
    }
}

編譯之後,可以發現從一維列表變成了網格列表,水果的樣式迴圈 10 次,效果如下:

GridView 示例

4. 小結

本節介紹了 Adapter 常用的第二個控制元件,它與 ListView 的用法及其相似,作為網格樣式,相比 ListView 有一些二維的屬性,其餘的 API 和事件回撥都和 ListView 完全一樣。最後我們完成了和 ListView 類似的一個“水果”列表樣式,通過修改 Adapter 可以達到擴充套件列表的效果。

這裡也希望大家能夠注意到 Adapter 的幾個回撥介面具體的含義,因為很多人雖然經常用 Adpater,但其實都是墨守成規的去實現那幾個回撥,沒有真正的理解其內涵,這樣在今後的實際開發中往往會遇到很多不可控的問題,希望大家能夠做到知其然並知其所以然。