listview嵌套listview制作目录
做一个学科知识点的目录功能,这里采用了listview嵌套listview的方式实现,这里记录一些做过程中遇到的值得记录下来的点,先看效果图:
主界面:
展开子知识点界面:
(1)listview加header,activity_subject_home_header.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:layout_gravity="center_horizontal" android:baselineAligned="false" android:orientation="horizontal" android:background="@color/subject_background" > <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="20dp" android:layout_marginBottom="10dp" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" > <TextView android:id="@+id/forecast_score_text_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="预测分" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/forecast_score_text_tv" android:layout_centerHorizontal="true" android:orientation="horizontal" > <TextView android:id="@+id/forecast_score_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="36" android:textSize="40sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="分" /> </LinearLayout> </RelativeLayout> </RelativeLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="20dp" android:layout_marginBottom="10dp" android:gravity="center_horizontal" > <View android:layout_width="2dp" android:layout_height="60dp" android:background="@color/subject_finish_text" /> </LinearLayout> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="20dp" android:layout_marginBottom="10dp" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" > <TextView android:id="@+id/beat_text_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="打败考生" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/beat_text_tv" android:layout_centerHorizontal="true" android:orientation="horizontal" > <TextView android:id="@+id/beat_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="25" android:textSize="40sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="%" /> </LinearLayout> </RelativeLayout> </RelativeLayout> </LinearLayout>
往listview加入:
/** * 接着上面说的添加header,添加header时调用的addHeaderView方法必须放在listview.setadapter前面,意思很明确就是如果想给listview添加头部则必须在给其绑定adapter前添加,否则会报错。原因是当我们在调用setAdapter方法时会android会判断当前listview是否已经添加header,如果已经添加则会生成一个新的tempadapter,这个新的tempadapter包含我们设置的adapter所有内容以及listview的header和footer。所以当我们在给listview添加了header后在程序中调用listview.getadapter时返回的是tempadapter而不是我们通过setadapter传进去的adapter。如果没有设置adapter则tempadapter与我们自己的adapter是一样的。listview.getadapter().getcount()方法返回值会比我们预期的要大,原因是添加了header。 */ LinearLayout hearderViewLayout = (LinearLayout)LayoutInflater.from(this).inflate(R.layout.activity_subject_home_header, null); rootPointListView.addHeaderView(hearderViewLayout, null, false);
2 点击外层listview(根知识点),展开子知识点的listview,原本子知识点的listview就存在布局中,只是平时是躲藏状态,点击后就将它的可视状态设置为visible,这里还得做一个事就是计算出子知识点的listview的高度:
if(isExpend[position] == true){ setListViewHeightBasedOnChildren(holder.pointsListView); holder.pointsListView.setVisibility(View.VISIBLE); holder.showKnowledgePointsImageView.setImageResource(R.drawable.slid_up); }else{ holder.pointsListView.setVisibility(View.GONE); holder.showKnowledgePointsImageView.setImageResource(R.drawable.slid_down); }
/** * 动态计算listview的高度 * @param listView */ public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)) + 20; listView.setLayoutParams(params); }
不重新计算子知识点的listview高度的话,那子listview是显示不出来的。
3 点击外层listview(根知识点)的一个item,将该item上移,不考虑其它情况的话,可以简单实现如下,
在点击item事件里加入如下代码:
int topPosition = ((LinearLayout)clickView.getParent()).getTop();//向顶端滑动 rootPointListView.smoothScrollBy(topPosition,200);
这里考虑的情况还包括了,点击了其中一个item,原来展开的item子listview也得关闭掉,这里就使用了getViewTreeObserver(注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等),等listview绘制完后,再将根知识点的item上移:
/** * 设置观察者,当listview重绘结束后,再重新计算listview的被点击item上移的高度,将该item顶到最顶部 */ rootPointListView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(clickView != null){ int topPosition = ((LinearLayout)clickView.getParent()).getTop();//向顶端滑动 rootPointListView.smoothScrollBy(topPosition,200); } } });
4 闭合一个展开的子知识点listview时,为了不让根知识点的listview产生抖动现象,这里得在点击事件时,将clickView设置为空,不让到走观察者的smoothScrollBy方法:
OnClickListener clickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.show_knowledge_points_layout: case R.id.root_subject_layout: int clickPosition = (Integer)v.getTag(); isExpend[clickPosition] = !isExpend[clickPosition]; if(isExpend[clickPosition] == true){//说明要展开 if(lastClickPosition != -1 && pointListViewList[lastClickPosition] != null && pointListViewList[lastClickPosition].getVisibility() == View.VISIBLE){ pointListViewList[lastClickPosition].setVisibility(View.GONE); CLog.d(TAG , "===============>visibale"); } /** * 记录点击的view,这里之所以记录下来,是因为我们上面将上次展开的子知识点合闭,这个变化,会引起计算item移动到顶部高度, * 所以不能在这里就将高度计算出来 topPosition = ((LinearLayout)clickView.getParent()).getTop(); * 而是放在OnGlobalLayoutListener里面 */ clickView = v; } else {//关闭展开的子知识点,这里将topPosition=0,让关闭时位置停留在原地,不会出现关闭抖动现像 clickView = null; } resetIsExpendArray(clickPosition);//重置点击的数组状态,只允许一个item子知识点展开 notifyDataSetChanged(); lastClickPosition = clickPosition; Log.e("RootPointsAdapter", "isExpend:" + (Integer)v.getTag() + " == true"); break; default: break; } } };
5 关闭软键盘,在AndroidManifest.xml里设置一下就行,不然,因为搜索的那个输入框的作为第一个能获取到焦点的控件需要打开软键盘,所以每次进这个页面都会打开软键盘,android:windowSoftInputMode="stateHidden"闭关它:
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.listviewdemo.MainActivity" android:label="@string/app_name" android:windowSoftInputMode="stateHidden" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
6 进度条圆环,看下一篇博文。
代码见附件