有时候总会遇到一些需求,例如需要在ListView中嵌套另外一个ListView,最近我就遇到了这种需求.但是本文的并不是写关于ListView嵌套的问题,而是ListView里面为动态布局的时候,如何进行View的复用
首先看一下需要实现的效果图:
可以看到上方红框部分为一个ListView,但是ListView里面貌似每一个Item都是有一些不一样的,例如第一个Item显示项只有一个,第二个item的项就有两个了,以此类推,一直到右图的六个,具体的每一个item大小如下图
在每个item中,除了白色部分包裹的内容为动态修改之外,其他内容的布局参数都是固定的.那么很快就可以想到这种实现方法,就是ListView里面嵌套另外一个ListView,可是ListView嵌套的方法有个弊端,就是里面一层的ListView并不是完全展示的,需要我们再次进行处理,重新设置内部的ListView的高度才可以显示正常.
因为里面一层Listview并没有涉及到交互,所以这里并没有用嵌套ListView的方法,而是利用LinearLayout实现了类似于ListView的效果.
其实item中白色部分是一个orientation="vertical"的LinearLayout,无论是用ListView或者LinearLayout实现,都会面临一个问题,就是数据量过多的时候不能过于频繁的去创建VIew,否则会出现卡顿的现象,在这里自然会很容易想到用Holder实现.
首先外面的ListView与平时所用的ListView没什么区别,贴上关键代码GetView():(后面的代码均是写在此方法里面)
@Override public View getView(int position, View convertView, ViewGroup parent) { final HolderPlan holder; final LayoutInflater layoutInflater = LayoutInflater.from(context); if (convertView == null) { convertView = layoutInflater.inflate(R.layout.item_assistant_plan, null, false); holder = new HolderPlan(convertView); convertView.setTag(holder); } else { holder = (HolderPlan) convertView.getTag(); }
Holder对象部分代码:
static class HolderPlan { @BindView(R.id.item_assistant_plan_list_point) ImageView ivPoint; @BindView(R.id.item_assistant_plan_time_month) TextView tvMonth; @BindView(R.id.item_assistant_plan_time_day) TextView tvDay; @BindView(R.id.item_assistant_plan_list_content) LinearLayout llListContent; HolderPlan(View view) { ButterKnife.bind(this, view); } }
其中代码
@BindView(R.id.item_assistant_plan_list_content) LinearLayout llListContent;是需要进行动态改变的,也就是其显示的ChildView的数量不确定.
既然ListView是用了Holder,所以llListContent的ChildView数量是不确定的,可能为0,也可能为若干个,所以这里需要对ChildView进行处理,当数量大于所需要的子View的数量的时候,可以Remove掉不需要的View,小于的时候,需要addView进去,但是这里为了频繁创建View对象,所以这里不采用移除的方法,而是设置显示与否来实现的,
//计划子项目的数量(目前暂时用position替代) final int contentCount = position + 1; final LinearLayout llList = holder.llListContent; //遍历父ViewGroup的左右子View,并且设置显示与否,达到View复用的目的 for (int i = 0; i < llList.getChildCount(); i++) { View childAt = llList.getChildAt(i); if (i < contentCount) { childAt.setVisibility(View.VISIBLE); } else { childAt.setVisibility(View.GONE);//不需要用到的View,不予显示 } }既然LinearLayout的子View也可以拿来重复用,所以也要对子View进行一个关于Holder的方法,来避免FindViewById进行查找.
所以有如下代码:
for (int i = 0; i < contentCount; i++) { final int childCount = llList.getChildCount(); final HolderPlanChild holderChild; if (i < childCount) { //如果需要子View的数量比llList目前所拥有子View的数量还要少,则直接取出View进行复用 View childAt = llList.getChildAt(i); holderChild = (HolderPlanChild) childAt.getTag(); } else { View view = layoutInflater.inflate(R.layout.item_assistant_plan_child, null, false); holderChild = new HolderPlanChild(view); view.setTag(holderChild); llList.addView(view); } //----子View设置显示内容标记处----// holderChild.tvContent.setText(contentCount + ":" + content[i]); holderChild.tvTime.setText("12:0" + i); //最后一项不予显示内容文字下方的分隔线 if (i == contentCount - 1) { holderChild.vLine.setVisibility(View.INVISIBLE); } else { holderChild.vLine.setVisibility(View.VISIBLE); } }
static class HolderPlanChild { @BindView(R.id.item_assistant_plan_child_time) TextView tvTime; @BindView(R.id.item_assistant_plan_child_point) View vPoint; @BindView(R.id.item_assistant_plan_child_content) TextView tvContent; @BindView(R.id.item_assistant_plan_child_bottom_line) View vLine; public HolderPlanChild(View view) { ButterKnife.bind(this, view); } }
具体的一些逻辑,在代码已经做了注释说明了.这里不多赘述.
最后分析一下一个弊端吧,就是当外层ListView的item中,白色部分每一项所需要的数量都差异很大时,对ListView进行了几次来回滚动,此时每一个item中的LinearLayout所拥有的ChildView数量可能是相同的,这会造成多余的内存开销.但是也有几个好处,就是造成卡顿现象减少,而且在每一个Item近似的时候,会有最好性能,这个就需要根据实际情况进行开发了.
下面贴上全部代码
ListView的Item的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="match_parent" android:background="@color/C_F6F7FB" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="48dp"> <TextView android:id="@+id/item_assistant_plan_time_month" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="15dp" android:text="2017/02" android:textColor="@color/C_959DA6" android:textSize="12sp" /> <TextView android:id="@+id/item_assistant_plan_time_day" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="37dp" android:includeFontPadding="false" android:text="02日02月" android:textColor="@color/C_959DA6" android:textSize="12sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/item_assistant_plan_list_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="9dp" android:background="@drawable/shape_white_10px_radius" android:orientation="vertical" /> <ImageView android:id="@+id/item_assistant_plan_list_point" android:layout_width="18dp" android:layout_height="18dp" android:layout_marginLeft="54dp" android:src="@mipmap/assistant_plan_dot1" /> </RelativeLayout> </LinearLayout>
内层白色部分包裹的每一个Item的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:orientation="horizontal"> <TextView android:id="@+id/item_assistant_plan_child_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="12.5dp" android:includeFontPadding="false" android:text="09:00" android:textColor="@color/C_8FA2FC" android:textSize="12sp" /> <RelativeLayout android:layout_width="8dp" android:layout_height="match_parent"> <View android:layout_width="1dp" android:layout_height="match_parent" android:layout_centerHorizontal="true" android:background="@color/line_gray_e9eaf2" /> <View android:id="@+id/item_assistant_plan_child_point" android:layout_width="8dp" android:layout_height="8dp" android:layout_centerHorizontal="true" android:layout_marginTop="15dp" android:background="@mipmap/assistant_plan_dot2_nor" /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="5dp" android:paddingRight="5dp"> <!--没有到时间点颜色为3c4550,过了时间点的为959da6--> <TextView android:id="@+id/item_assistant_plan_child_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="12dp" android:layout_marginTop="10dp" android:text="约陈女士到店." android:textColor="@color/C_3C4550" android:textSize="14sp" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:id="@+id/item_assistant_plan_child_bottom_line" android:background="@color/line_gray_e9eaf2" /> </LinearLayout> </LinearLayout>
ListView的Adapter部分的Java代码:
/** * 此方法实现的大体思路,在下面代码的标记处之前的部分,跟大都代码一样,采用holder机制,并且处理相应的View, * 标记处之后的代码则实现要展示内容的子View生成,以及复用功能(holder). * 展示内容子LinearLayout的View的生成:因为getView方法采用了Holder机制,所以内容的子View是有可能存在, * 并且有需要的子View可能大于/小于/等于LinearLayout目前所拥有的子View数量,这部分要进行展示与否,即设置Gone或者Visibility部分代码. * 若所需数量大于目前LinearLayout已有的子View数量,则进行添加操作.其他已有部分则取出进行复用. * 在"子View设置显示内容标记处"则是处理每一个展示计划内容的代码 * * @param position * @param convertView * @param parent * @return */ @Override public View getView(int position, View convertView, ViewGroup parent) { final HolderPlan holder; final LayoutInflater layoutInflater = LayoutInflater.from(context); if (convertView == null) { convertView = layoutInflater.inflate(R.layout.item_assistant_plan, null, false); holder = new HolderPlan(convertView); convertView.setTag(holder); } else { holder = (HolderPlan) convertView.getTag(); } holder.tvMonth.setText(("2017/" + position)); final SpannableString msp = new SpannableString("02日03月"); // 2.0f表示默认字体大小的两倍 msp.setSpan(new RelativeSizeSpan(1.5f), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 设置字体前景色 ,Color.MAGENTA为紫红 msp.setSpan(new ForegroundColorSpan(0xFF8FA2FC), 0, msp.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); holder.tvDay.setText(msp); /*-----子View处理标记处----*/ //计划子项目的数量(目前暂时用position替代) final int contentCount = position + 1; final LinearLayout llList = holder.llListContent; //遍历父ViewGroup的左右子View,并且设置显示与否,达到View复用的目的 for (int i = 0; i < llList.getChildCount(); i++) { View childAt = llList.getChildAt(i); if (i < contentCount) { childAt.setVisibility(View.VISIBLE); } else { childAt.setVisibility(View.GONE);//不需要用到的View,不予显示 } } for (int i = 0; i < contentCount; i++) { final int childCount = llList.getChildCount(); final HolderPlanChild holderChild; if (i < childCount) { //如果需要子View的数量比llList目前所拥有子View的数量还要少,则直接取出View进行复用 View childAt = llList.getChildAt(i); holderChild = (HolderPlanChild) childAt.getTag(); } else { View view = layoutInflater.inflate(R.layout.item_assistant_plan_child, null, false); holderChild = new HolderPlanChild(view); view.setTag(holderChild); llList.addView(view); } //----子View设置显示内容标记处----// holderChild.tvContent.setText(contentCount + ":" + content[i]); holderChild.tvTime.setText("12:0" + i); //最后一项不予显示内容文字下方的分隔线 if (i == contentCount - 1) { holderChild.vLine.setVisibility(View.INVISIBLE); } else { holderChild.vLine.setVisibility(View.VISIBLE); } } return convertView; } private static final String[] content = { "约陈女士到店服务", "赵女士今日11:00到店,体验德国seyo的仪器效果", "房东水电报销", "天理点的顾客到这边体验,不喜欢吵闹", "中午午休,什么也不做", "和同事开黑王者荣耀,我打辅助", "下午茶时间,吃好喝好,然后去睡觉", "换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行", "出去旅游,坐大巴,然后带纪念品回来", "什么也不想做,只想发呆", "喝水呛到,然后请假休息了半天" }; static class HolderPlanChild { @BindView(R.id.item_assistant_plan_child_time) TextView tvTime; @BindView(R.id.item_assistant_plan_child_point) View vPoint; @BindView(R.id.item_assistant_plan_child_content) TextView tvContent; @BindView(R.id.item_assistant_plan_child_bottom_line) View vLine; public HolderPlanChild(View view) { ButterKnife.bind(this, view); } } static class HolderPlan { @BindView(R.id.item_assistant_plan_list_point) ImageView ivPoint; @BindView(R.id.item_assistant_plan_time_month) TextView tvMonth; @BindView(R.id.item_assistant_plan_time_day) TextView tvDay; @BindView(R.id.item_assistant_plan_list_content) LinearLayout llListContent; HolderPlan(View view) { ButterKnife.bind(this, view); } }