編輯:關於Android編程
在做項目的時候,想在 ExpandableListView 中嵌套一個 GridView,在實現的過程中,遇到了不少坑,所以寫篇博客記錄一下,也順便幫助下和我一樣的新手。
我一直覺得,再多的文字,再多的代碼片段,都不如寫個小 Demo 更直觀,所以在以後的博客中,我都盡量用小 Demo 來講解,也給出源碼。
先上一張最終效果圖:
打開 Android Studio,我們新建一個 ExpandableListViewTest 項目,在項目中新建兩個包(package), 分別命名為 activity 和 adapter,然後將 Android Studio 默認創建的 MainActivity 拖入 activity 包中。
為了讓項目運行起來看上去更美觀,我們用 Theme 主題,修改 AndroidManifest.xml 中的 application 下的 android:theme 屬性,修改為 @android:style/Theme,
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
修改後的 AndroidManifest.xml 代碼如下所示:
修改了主題後,我們再修改下 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 的第一個子項時,都顯示的第 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 標簽。
替換後的 expandablelist_item.xml 代碼如下:
重新運行程序,我們發現,GridView 已經完美嵌套在 ExpandableListView 中了。
序:本文講RelativeLayout兩點:1. 簡單例子說明RelativeLayout使用方法 2.強調 用gravity 而不是用layout_gravity來總體
Android開發中視圖和數據的綁定離不開Adapt系列的類,在呈現給用戶的界面友好美觀和內容豐富的應用中視圖為骨,內容為肉,Ad
一.昨天,介紹了使用MediaMetadataRetriever類來獲取視頻第一幀:http://blog.csdn.net/u012561176/article/det
本文給大家介紹在Android中如何實現頂部導航菜單左右滑動效果,具體內容如下第一種解決方案: 實現原理是使用android-support-v4.jar包中ViewPa