Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ExpandableListView 中完美嵌套 GridView

ExpandableListView 中完美嵌套 GridView

編輯:關於Android編程

在做項目的時候,想在 ExpandableListView 中嵌套一個 GridView,在實現的過程中,遇到了不少坑,所以寫篇博客記錄一下,也順便幫助下和我一樣的新手。

我一直覺得,再多的文字,再多的代碼片段,都不如寫個小 Demo 更直觀,所以在以後的博客中,我都盡量用小 Demo 來講解,也給出源碼。

先上一張最終效果圖:

完美嵌套

打開 Android Studio,我們新建一個 ExpandableListViewTest 項目,在項目中新建兩個包(package), 分別命名為 activity 和 adapter,然後將 Android Studio 默認創建的 MainActivity 拖入 activity 包中。

為了讓項目運行起來看上去更美觀,我們用 Theme 主題,修改 AndroidManifest.xml 中的 application 下的 android:theme 屬性,修改為 @android:style/Theme,
修改後的 AndroidManifest.xml 代碼如下所示:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">

修改了主題後,我們再修改下 MainActivity.java 文件,MainActivity 默認繼承自 AppCompatActivity ,我們把 AppCompatActivity 修改為 Activity,讓 MainActivity 繼承 Activity,這樣等會項目才能正常運行。

我們先來寫一下布局文件,既然我們想在 ExpandableListView 中嵌套一個 GridView,那麼首先我們應該寫一個 ExpandableListView

activity_main.xml 代碼如下所示:




    

布局文件非常簡單,在布局中只放置了一個 ExpandableListView,並設置了各組之間的分割線和各組子項之間的分割線,分割線是一張圖片。接下來我們就要在 MainActivity 中實例化 ExpandableListView 的布局,並准備好 ExpandableListView 要顯示的數據,然後把數據傳入 ExpandableListView 的適配器,這樣才能把 ExpandableListView 顯示出來。

MainActivity.java 代碼如下所示:

package com.example.expandablelistviewtest.activity;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;

import com.example.expandablelistviewtest.R;
import com.example.expandablelistviewtest.adapter.MyExpandableListViewAdapter;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private ExpandableListView expandableListView;

    /**
     * 每個分組的名字的集合
     */
    private List groupList;

    /**
     * 每個分組下的每個子項的 GridView 數據集合
     */
    private List itemGridList;

    /**
     * 所有分組的所有子項的 GridView 數據集合
     */
    private List> itemList;

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

        expandableListView = (ExpandableListView) findViewById(R.id.expandableList);

        // 分組
        groupList = new ArrayList<>();
        groupList.add("分組1");
        groupList.add("分組2");

        // 每個分組下的每個子項的 GridView 數據集合
        itemGridList = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            itemGridList.add("電腦" + (i + 1));
        }

        // 所有分組的所有子項的 GridView 數據集合
        itemList = new ArrayList<>();
        itemList.add(itemGridList);
        itemList.add(itemGridList);
        // 創建適配器
        MyExpandableListViewAdapter adapter = new MyExpandableListViewAdapter(MainActivity.this,
                groupList, itemList);
        expandableListView.setAdapter(adapter);
        // 隱藏分組指示器
        expandableListView.setGroupIndicator(null);
        // 默認展開第一組
        expandableListView.expandGroup(0);
    }
}

既然我們要設置 ExpandableListView 適配器,那首先我們要定義一個 ExpandableListView 適配器,在 adapter 包下新建一個 MyExpandableListViewAdapter.java 類,在 ExpandableListView 適配器中,我們將把在 MainActivity 中准備好的數據傳入這個適配器,並定義這些數據該怎麼顯示,因此我們還要創建一個 ExpandableListView 的分組布局文件 和 子項布局文件。不過不要著急,我們先定義好適配器。

MyExpandableListViewAdapter.java 代碼如下所示:

package com.example.expandablelistviewtest.adapter;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseExpandableListAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.expandablelistviewtest.R;

import java.util.List;

/**
 * ExpandableListView 適配器
 */
public class MyExpandableListViewAdapter extends BaseExpandableListAdapter {

    private Context mContext;

    /**
     * 每個分組的名字的集合
     */
    private List groupList;

    /**
     * 所有分組的所有子項的 GridView 數據集合
     */
    private List> itemList;

    private GridView gridView;

    public MyExpandableListViewAdapter(Context context, List groupList,
                                       List> itemList) {
        mContext = context;
        this.groupList = groupList;
        this.itemList = itemList;
    }

    @Override
    public int getGroupCount() {
        return groupList.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return itemList.get(groupPosition).size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return groupList.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return itemList.get(groupPosition).get(childPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup
            parent) {
        if (null == convertView) {
            convertView = View.inflate(mContext, R.layout.expandablelist_group, null);
        }
        ImageView ivGroup = (ImageView) convertView.findViewById(R.id.iv_group);
        TextView tvGroup = (TextView) convertView.findViewById(R.id.tv_group);
        // 如果是展開狀態,就顯示展開的箭頭,否則,顯示折疊的箭頭
        if (isExpanded) {
            ivGroup.setImageResource(R.drawable.ic_open);
        } else {
            ivGroup.setImageResource(R.drawable.ic_close);
        }
        // 設置分組組名
        tvGroup.setText(groupList.get(groupPosition));
        return convertView;
    }

    @Override
    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View
            convertView, ViewGroup parent) {
        if (null == convertView) {
            convertView = View.inflate(mContext, R.layout.expandablelist_item, null);
        }
        // 因為 convertView 的布局就是一個 GridView,
        // 所以可以向下轉型為 GridView
        gridView = (GridView) convertView;
        // 創建 GridView 適配器
        MyGridViewAdapter gridViewAdapter = new MyGridViewAdapter(mContext, itemList.get
                (groupPosition));
        gridView.setAdapter(gridViewAdapter);
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                Toast.makeText(mContext, "點擊了第" + (groupPosition + 1) + "組,第" +
                        (position + 1) + "項", Toast.LENGTH_SHORT).show();
            }
        });
        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return false;
    }
}

我們可以看到,ExpandableListView 的適配器代碼看上去很多,但實際上並不復雜,因為大部分代碼都是重寫父類的方法,這裡對 getChildView 的某些代碼解釋一下:convertView 的布局就是一個 GridView,但是實例化布局後,convertView 是 View 類型,所以 convertView 沒有 setAdapter() 方法,需要向下轉型為 GridView ,才能調用 setAdapter() 方法,設置適配器。

定義好了 ExpandableListView 的適配器,我們來寫一下 ExpandableListView 的分組布局文件 和 子項布局文件。我們先寫 ExpandableListView 的分組布局文件,在 layout 文件夾下,新建 expandablelist_group.xml ,代碼如下所示:




    

    

我們可以看到,分組布局非常簡單,就是一個橫向的線性布局,然後依次排列了一個 指示箭頭 圖片 和一個 分組 名字,我們繼續寫 ExpandableListView 的子項布局文件,在 layout 文件夾下,新建 expandablelist_item.xml ,代碼如下所示:




我們可以看到,子項布局更簡單,就是一個有 3 列數據的 GridView。

對了,剛才我們在 ExpandableListView 的適配器中創建了 GridView 的適配器,那我們趕緊去定義一下 GridView 的適配器吧。

在 adapter 包下,新建 MyGridViewAdapter.java ,代碼如下所示:

package com.example.expandablelistviewtest.adapter;

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

import com.example.expandablelistviewtest.R;

import java.util.List;

/**
 * GridView 適配器
 */
public class MyGridViewAdapter extends BaseAdapter {

    private Context mContext;

    /**
     * 每個分組下的每個子項的 GridView 數據集合
     */
    private List itemGridList;

    public MyGridViewAdapter(Context mContext, List itemGridList) {
        this.mContext = mContext;
        this.itemGridList = itemGridList;
    }

    @Override
    public int getCount() {
        return itemGridList.size();
    }

    @Override
    public Object getItem(int position) {
        return itemGridList.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (null == convertView) {
            convertView = View.inflate(mContext, R.layout.gridview_item, null);
        }
        TextView tvGridView = (TextView) convertView.findViewById(R.id.tv_gridview);
        tvGridView.setText(itemGridList.get(position));
        return convertView;
    }
}

GridView 的適配器和 ExpandableListView 的適配器一樣,邏輯並不復雜,主要就是重寫父類的方法,在 getView() 方法中,我們實例化了 GridView 的子項布局,所以,我們也要寫一下 GridView 的子項布局文件,在 layout 文件夾中,新建 gridview_item.xml ,代碼如下所示:




    

GridView 子項布局只定義了一個帶有圖片的 TextView。

好了,我們來運行一下,看看效果。

初步運行效果

好像已經成功了,不過貌似有些問題:

它的每個分組的每個子項裡都有一個 GridView,但我們只想在每個分組裡只顯示一個 GridView 。 在點擊 GridView 中的子項時,我們發現,在同一個分組裡,點擊不同子項裡 GridView 的第一個子項時,都顯示的第 N 組第 1 項。 我們定義的 GridView 有 4 個數據,分別是 電腦 1 ,電腦 2 ,電腦 3 ,電腦 4 。但是它只顯示了 3 個。

我們逐一解決這些問題。

首先,我們可以發現,每組都有 4 行數據,也就是說,每組都有 4 個子項,那麼如果我們想要在每個分組裡只顯示一個 GridView ,也就是一個子項,那麼我們要先找到我們之前定義過的和 4 相關的數據,經過查找,我們發現,和 4 相關的數據只有一個,就是 itemGridList ,它裡面有 4 個數據,找到了它之後,我們再找一下它在哪裡被調用了,經過查找,我們發現,在 ExpandableListView 適配器中的 getChildrenCount() 方法中調用了它,有些同學了可能會疑惑,這裡調用的是 itemList.get(groupPosition).size(),關 itemGridList 什麼事,其實我們之前在 MainActivity 中是這樣創建 itemList 的:itemList.add(itemGridList),在這行代碼中,itemGridList 被添加到了 itemList 中,那麼在 getChildrenCount() 方法中,調用 itemList.get(groupPosition) ,得到的就是 itemGridList ,再調用它的 size() 方法,得到的就是 itemGridList 的大小了,也就是 4,知道了這個,我們想在每個分組裡只顯示一個 GridView,也就好辦了,在 getChildrenCount() 方法中,我們直接返回 1,即

@Override
public int getChildrenCount(int groupPosition) {
    return 1;
}

再運行一下程序,我們發現,果然每個分組裡只顯示了一個 GridView 。

每個分組只顯示一個 GridView

我們再來看第二個問題,在同一個分組裡,點擊不同子項裡 GridView 的第一個子項時,都顯示的第 N 組第 1 項。

我們查看下 ExpandableListView 適配器代碼中的 getChildView() 代碼中的設置子項監聽器代碼:

Toast.makeText(mContext, "點擊了第" + (groupPosition + 1) + "組,第" +
                        (position + 1) + "項", Toast.LENGTH_SHORT).show();

這裡使用了兩個變量, groupPosition 很容易理解,就是 分組的 id ,position 可能有些同學會迷惑,position 並不是分組的子項 id ,而是 GridView 的子項 id ,弄清了這個,第二個問題也就不難懂了,我們不管點擊的是分組下的哪一個子項中的 GridView 的第一項,都顯示是 第一項,是因為 position 表示的 GridView 的子項 id ,和 分組下的子項 id 並沒有關系。

我們再來看第三個問題,我們定義的 GridView 有 4 個數據,但是它只顯示了 3 個。

首先,它只顯示 3 個的部分原因是因為我們定義的 GridView 一行只能顯示 3 個數據,我們可以查看 expandablelist_item.xml 的代碼:android:numColumns="3"

更重要的原因是因為 GridView 的父容器測量模式為 UNSPECIFIED 的時候,GridView 的高度默認為一個 item 的高度,GridView 中源碼如下:

if (heightMode == MeasureSpec.UNSPECIFIED) {
    heightSize = mListPadding.top + mListPadding.bottom + childHeight +
            getVerticalFadingEdgeLength() * 2;
}

既然是這樣的話,我們就重寫 GridView 的 onMeasure 方法,自定義高度,在 activity 包中,新建 MyGridView.java ,代碼如下:

package com.example.expandablelistviewtest.activity;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;

/**
 * 自定義 GridView
 */
public class MyGridView extends GridView {

    public MyGridView(Context context) {
        super(context);
    }

    public MyGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 重寫該方法,達到使 GridView 適應 ExpandableListView 的效果
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
}

關於重寫 GridView 的 onMeasure 方法的原理分析,可以查看這兩篇文章。

android開發游記:ScrollView嵌套ListView,ListView完全展開及makeMeasureSpec測量機制原理分析

詳解嵌套ListView、ScrollView布局顯示不全的問題

寫好了 MyGridView 後,在 MyGridView 上點擊右鍵,選擇 Copy Reference,復制 MyGridView 的全路徑名,替換 expandablelist_item.xml 中的 GridView 標簽。

復制 GridView 全路徑名

替換後的 expandablelist_item.xml 代碼如下:




重新運行程序,我們發現,GridView 已經完美嵌套在 ExpandableListView 中了。

完美嵌套

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved