前两天无意间看到朋友的一个功能要实现分类列表,也就互相简单的聊了一下,在此感觉还是挺有意思又加上有段时间没写这个了,就想着用自己的方法实现一下,下面是UI效果图
一、分析实现
其实可以直接使用
ExpandableListView
(从出来工作写代码开始算起我用到ExpandableListView
的次数不超过一个巴掌,以下就全当我不会使用这个控件吧!),不会使用这个控件而且我又只会ListView咋办呢,别急,先观察一下UI效果和数据。
{"msg":"可用优惠券列表获取成功","code":"100","data":[{"isUse":"1","useConditions":50.00,"couponUserId":2237,"couponName":"满50减15","validityDate":"2019-06-26至2019-07-31","couponUseExplain":"优选商城通用,团购/抢购不可用","denominationShow":"15","useConditionsDesc":"满50.00可用","couponId":362,"useConditionsShow":"50","denomination":15.00},{"isUse":"1","useConditions":10.00,"couponUserId":2236,"couponName":"满10减5","validityDate":"2019-06-26至2019-07-31","couponUseExplain":"优选商城通用,团购/抢购不可用","denominationShow":"5","useConditionsDesc":"满10.00可用","couponId":361,"useConditionsShow":"10","denomination":5.00},{"isUse":"1","useConditions":5.00,"couponUserId":2235,"couponName":"满5减2元","validityDate":"2019-06-26至2019-07-31","couponUseExplain":"优选商城通用,团购/抢购不可用","denominationShow":"2","useConditionsDesc":"满5.00可用","couponId":360,"useConditionsShow":"5","denomination":2.00},{"isUse":"2","useConditions":200.00,"couponUserId":2238,"couponName":"满200减70","validityDate":"2019-06-26至2019-07-31","couponUseExplain":"优选商城通用,团购/抢购不可用","denominationShow":"70","useConditionsDesc":"满200.00可用","couponId":363,"useConditionsShow":"200","denomination":70.00}],"success":true,"now":11111111111}
- 数据分类,从数据中可以看出,主要分为两类数据:
1)isUse=1是当前可用
2)isUse=2是不满足条件 - 对应数据分类,使用ViewType来区分为两种item视图进行绘制:
1)优惠券的视图;
2)分类title的视图; - 视图绘制,大家都知道使用ViewType都是用来显示参差不齐的数据显示(也就是无序),而我们是要分类有序的显示,所以我们对数据根据
isUse
排序分类显示,但是在每个分类的前都是title类,所以只要我们的分类Title
排序后正好在每个对应类型的前面就行了;至此麻烦来了,在数据里是没有关于title的数据,所以我们还要添加对应分类Title
假数据在对其进行排序,由于两类数据都是根据isUse来分类的且值分别为1、2
,我们可以创建两个对应的假数据将其isUser设置为0、1.5
然后在用Float.compare()
实现来对应进行排序,在对其进行适配绘制就完成了。
二、编码
上面对其进行里简单的分析,下面开始进行愉快的编码吧
-
解析数据
分析完数据我们就开始创建对应的实体类:
1)响应基类BaseResponse
让其实现序列化接口Serializable
,赋予范型T
成为范型类,T
可以对应我们任何类型的响应数据,可以是List<T>
、Object
等public class BaseResponse<T> implements Serializable { /** * msg : 可用优惠券列表获取成功 * code : 100 * data : items * success : true * now : 1561625083199 */ private String msg; private String code; private boolean success; private long now; private T data; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public long getNow() { return now; } public void setNow(long now) { this.now = now; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
2)对此次数据类型模型应该是
BaseRespons<List<Item>>
,所以我们接下来创建我们Item
类-CouponBean
且实现序列化接口Serializable
,最终需要解析的数据模型为:BaseRespons<List<CouponBean>>
public class CouponBean implements Serializable { /** * isUse : 1 * useConditions : 50.0 * couponUserId : 2237 * couponName : 满50减15 * validityDate : 2019-06-26至2019-07-31 * couponUseExplain : 优选商城通用,团购/抢购不可用 * denominationShow : 15 * useConditionsDesc : 满50.00可用 * couponId : 362 * useConditionsShow : 50 * denomination : 15.0 */ private String isUse; private double useConditions; private int couponUserId; private String couponName; private String validityDate; private String couponUseExplain; private String denominationShow; private String useConditionsDesc; private int couponId; private String useConditionsShow; private double denomination; public String getIsUse() { return isUse; } public void setIsUse(String isUse) { this.isUse = isUse; } public double getUseConditions() { return useConditions; } public void setUseConditions(double useConditions) { this.useConditions = useConditions; } public int getCouponUserId() { return couponUserId; } public void setCouponUserId(int couponUserId) { this.couponUserId = couponUserId; } public String getCouponName() { return couponName; } public void setCouponName(String couponName) { this.couponName = couponName; } public String getValidityDate() { return validityDate; } public void setValidityDate(String validityDate) { this.validityDate = validityDate; } public String getCouponUseExplain() { return couponUseExplain; } public void setCouponUseExplain(String couponUseExplain) { this.couponUseExplain = couponUseExplain; } public String getDenominationShow() { return denominationShow; } public void setDenominationShow(String denominationShow) { this.denominationShow = denominationShow; } public String getUseConditionsDesc() { return useConditionsDesc; } public void setUseConditionsDesc(String useConditionsDesc) { this.useConditionsDesc = useConditionsDesc; } public int getCouponId() { return couponId; } public void setCouponId(int couponId) { this.couponId = couponId; } public String getUseConditionsShow() { return useConditionsShow; } public void setUseConditionsShow(String useConditionsShow) { this.useConditionsShow = useConditionsShow; } public double getDenomination() { return denomination; } public void setDenomination(double denomination) { this.denomination = denomination; } }
-
处理数据,之前分析我们不仅要添加对应的类型假数据,还需要根据isUse对数据进行类别的排序,这样我们才能保证ListView绘制出来是有效有序的分类列表。
1)利用gson解析json数据,首先获取对应类型type:
Type type = new TypeToken<BaseResponse<List<CouponBean>>>() { }.getType();
再者根据gson提供的api:public <T> T fromJson(String json, Type typeOfT)
对其进行解析:BaseResponse<List<CouponBean>> baseResponse = new Gson().fromJson(json_data, type)
,这样就得到我们要的数据,然后是添加类别假数据在进行排序,在上面我们已经分析了如何进行排序,就是创建两个对应假数据CouponBean
分别将其isUse设置为0、1.5
,利用它的couponName
属性来存储我们的分类title:当前可用、不满足条件
,在用利用Collections.sort(Float.compare())
对其进行排序为Collections.sort(data, (o1, o2) -> Float.valueOf(o1.getIsUse()).compareTo(Float.valueOf(o2.getIsUse())))
,在此为了更优雅,我们模拟远程服务器(不模拟延时)创建本地服务类:LocalDataServer
如下:public final class LocalDataServer { static final String json_data = "{\"msg\":\"可用优惠券列表获取成功\",\"code\":\"100\",\"data\":[" + "{\"isUse\":\"1\",\"useConditions\":50.00,\"couponUserId\":2237,\"couponName\":\"满50减15\",\"validityDate\":\"2019-06-26至2019-07-31\",\"couponUseExplain\":\"优选商城通用,团购/抢购不可用\",\"denominationShow\":\"15\",\"useConditionsDesc\":\"满50.00可用\",\"couponId\":362,\"useConditionsShow\":\"50\",\"denomination\":15.00}," + "{\"isUse\":\"1\",\"useConditions\":10.00,\"couponUserId\":2236,\"couponName\":\"满10减5\",\"validityDate\":\"2019-06-26至2019-07-31\",\"couponUseExplain\":\"优选商城通用,团购/抢购不可用\",\"denominationShow\":\"5\",\"useConditionsDesc\":\"满10.00可用\",\"couponId\":361,\"useConditionsShow\":\"10\",\"denomination\":5.00}," + "{\"isUse\":\"1\",\"useConditions\":5.00,\"couponUserId\":2235,\"couponName\":\"满5减2元\",\"validityDate\":\"2019-06-26至2019-07-31\",\"couponUseExplain\":\"优选商城通用,团购/抢购不可用\",\"denominationShow\":\"2\",\"useConditionsDesc\":\"满5.00可用\",\"couponId\":360,\"useConditionsShow\":\"5\",\"denomination\":2.00}," + "{\"isUse\":\"2\",\"useConditions\":200.00,\"couponUserId\":2238,\"couponName\":\"满200减70\",\"validityDate\":\"2019-06-26至2019-07-31\",\"couponUseExplain\":\"优选商城通用,团购/抢购不可用\",\"denominationShow\":\"70\",\"useConditionsDesc\":\"满200.00可用\",\"couponId\":363,\"useConditionsShow\":\"200\",\"denomination\":70.00}]," + "\"success\":true,\"now\":1561625083199}"; /** * 获取根据ViewType分类显示的数据 * * @return */ public static List<CouponBean> requestDateFromServerByViewType() { BaseResponse<List<CouponBean>> baseResponse = new Gson().fromJson(json_data, new TypeToken<BaseResponse<List<CouponBean>>>() { }.getType()); List<CouponBean> data = baseResponse.getData(); CouponBean couponBean = new CouponBean(); couponBean.setIsUse("0"); couponBean.setCouponName("当前可用"); data.add(couponBean); couponBean = new CouponBean(); couponBean.setIsUse("1.5"); couponBean.setCouponName("当前不满足条件"); data.add(couponBean); Collections.sort(data, (o1, o2) -> Float.valueOf(o1.getIsUse()).compareTo(Float.valueOf(o2.getIsUse()))); return data; }
-
布局文件(没啥好说的直接贴吧)
1)activity布局文件:<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".yunfei.ListViewActivity"> <ListView android:id="@+id/listView" android:divider="@null" android:layout_width="match_parent" android:background="@color/main_radio_selected" android:layout_height="match_parent"/> </RelativeLayout>
2)title分类item布局文件(因为和我讨论哥们叫云飞所以加了个云飞?):
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/yunfei_title" android:layout_width="match_parent" android:gravity="center_vertical" android:textSize="@dimen/sp18" android:textColor="@color/blue" android:paddingLeft="@dimen/dp15" android:paddingTop="@dimen/dp10" android:layout_height="wrap_content"/>
3)coupon分类item布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:paddingTop="@dimen/dp15" android:paddingLeft="@dimen/dp15" android:paddingRight="@dimen/dp15" android:background="@color/main_radio_selected" android:layout_height="wrap_content"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="@dimen/dp60" app:cardBackgroundColor="@color/white" app:cardElevation="0dp" app:cardCornerRadius="@dimen/dp20" app:contentPaddingLeft="@dimen/dp20" app:contentPaddingRight="@dimen/dp20" app:contentPaddingTop="@dimen/dp20" tools:ignore="HardcodedText,UnusedAttribute"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:maxEms="7" android:maxLines="1" android:text="新用户优惠券新的生活" android:textSize="@dimen/sp20" /> <TextView android:id="@+id/couponDescription" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp5" android:contentDescription="优惠券说明" android:ellipsize="end" android:maxEms="11" android:maxLines="1" android:text="优选商城通用,团购/抢购不可用" android:textSize="@dimen/sp15" /> </LinearLayout> <TextView android:id="@+id/couponMoney" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:contentDescription="优惠券金额" android:gravity="center" android:text="50" android:textSize="@dimen/sp50" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="@dimen/px1" android:background="@color/black" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:paddingTop="@dimen/dp10" android:paddingBottom="@dimen/dp10"> <TextView android:id="@+id/indate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="有效期" android:text="2017-12-12至2019-09-05" /> <TextView android:id="@+id/useCondition" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:contentDescription="满多少才能使用,使用条件" android:text="满200可用" /> </RelativeLayout> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
此分类在AndroidStudio显示效果为(因为是白色为了显示CardView,加了个绿背景色,并没有照UI图全部搭建效果,我们主要目的还是实现分类显示):
-
编码适配器
ListViewAdapter
1)创建ListViewAdapter继承BaseAdapter,添加List<CouponBean> mCouponBeanList
和Context mContext
属性,创建这两个参数的构造方法,先重写public int getCount()
、public Object getItem(int position)
和public long getItemId(int position)
方法为:public class ListViewAdapter extends BaseAdapter { private List<CouponBean> mCouponBeanList; private Context mContext; public ListViewAdapter(List<CouponBean> couponBeanList, Context context) { mCouponBeanList = couponBeanList; mContext = context; } @Override public int getCount() { if (mCouponBeanList != null) { return mCouponBeanList.size(); } return 0; } @Override public Object getItem(int position) { return mCouponBeanList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { return null; }
2)因为我们是根据ViewType来进行分类显示的,所以我们重写方法
public int getViewTypeCount()
和public int getItemViewType(int position)
,由于我们只有两个类型所以ViewTypeCount()
要返回2
,注意:确定返回类型数量n后,getItemViewType返回的值必须在区间[0,n-1]之间,也就是说getItemViewType
只能返回0
或者1
,我将isUse=0或isUse=1.5
的分为第一类其值返回0
即getItemViewType=0
,isUse=1或isUse=2
为第二类其值返回1
即getItemViewType=1
,在这里插入代码片
代码如下:@Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { String isUse = mCouponBeanList.get(position).getIsUse(); return ("0".equals(isUse) || "1.5".equals(isUse)) ? 0 : 1; }
3)创建视图View,根据ViewType我们有两个分类,所以需要创建填充两个视图,
ViewType=0的对应title分类标题视图
;ViewType=1的对应coupon优惠券视图
,出于对内存等性能考虑,我们需要进行View的复用,所以我们还需要创建两个ViewHolder,其分别对应两个不同ViewType:创建分类标题ViewHolder:TitleViewHolder
对应标题布局文件ViewType=0
,解析数据映射到对应的控件上代码为:static class TitleViewHolder { private TextView title; TitleViewHolder(View itemView) { title = itemView.findViewById(R.id.yunfei_title); } void bindData(CouponBean couponBean) { title.setText(couponBean.getCouponName()); } }
创建分类标题CouponViewHolder:
CouponViewHolder
对应优惠券布局文件ViewType=1
,解析数据映射到对应的控件上代码为:static class CouponViewHolder { private TextView couponDescription, couponMoney, indate, useCondition; CouponViewHolder(View itemView) { couponDescription = itemView.findViewById(R.id.couponDescription); couponMoney = itemView.findViewById(R.id.couponMoney); useCondition = itemView.findViewById(R.id.useCondition); indate = itemView.findViewById(R.id.indate); } void bindData(CouponBean couponBean) { couponDescription.setText(couponBean.getCouponUseExplain()); couponMoney.setText(couponBean.getDenominationShow()); useCondition.setText(couponBean.getUseConditionsDesc()); indate.setText(couponBean.getValidityDate()); } }
最后重写我们的
public View getView(int position, View convertView, ViewGroup parent)
方法获视图:@Override public View getView(int position, View convertView, ViewGroup parent) { return getItemViewType(position) == 0 ? getTitleView(position, convertView, parent) : getCouponView(position, convertView, parent); }
三、测试效果
- 在Activity中给ListView设置适配器:
List<CouponBean> couponBeans = LocalDataServer.requestDateFromServerByViewType(); ListViewAdapter listViewAdapter = new ListViewAdapter(couponBeans, this); listView.setAdapter(listViewAdapter);
- 运行效果为:
至此我们使用
ListView-ViewType
来实现分类显示就完成了,下二篇,我们将继续实现此功能,不过使用分别是RecyclerView-ViewType
和RecyclerView-ItemDecoration
来实现。如果本篇文章有什么技术上问题还请留言讨论共同进步谢谢!
`