热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ListView的分组实现

利用StickyListHeaders来实现ListView的分组实现分类:AndroidDemo2014-03-0817:37244人阅读评论(1)收藏举报StickyListHead
利用StickyListHeaders来实现ListView的分组实现 分类: Android Demo 244人阅读 评论(1)收藏 举报 StickyListHeadersAndroidSectionIndexerListView分组

很多情况下, 我们想要ListView上面展示的东西是可以分组的,比如联系人列表,国家列表啊,这样看起来数据的展现比较有层次感,而且也有助于我们快速定位到某一个具体的条目上,具体效果请看下图:


这是前面TodoList小demo的MainActivity,主要是来展现用户添加的任务的,在原来的基础上添加了分组的效果。

接下来我们具体来讲一下这个效果是怎么实现的。

这是利用开源库StickyListHeaders(传送门:https://github.com/emilsjolander/StickyListHeaders)来实现的,这个实现的效果是基于ListView的,而其实也有关于GridView而实现的分组的效果,大家可以参考一下xiaanming的博客(他的文章名字都很长。。。):

Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果

0)关于如何导进开源库,大家请参考:如何导进开源库StickyListHeaders

1)然后,我们要想清楚一件事情,即分组的ListView,是包含两部分:Header 和 Item,所以相对应的我们也要为其定义两个Layout,如下:

1.1)task_header.xml

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="@drawable/header_selector" >  
  6.   
  7.     <TextView  
  8.         android:id="@+id/tvHeader"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="match_parent"  
  11.         android:layout_gravity="start|left"  
  12.         android:padding="5dp"  
  13.         android:textColor="@android:color/white"  
  14.         android:textSize="17sp"  
  15.         android:textStyle="bold" />  
  16.   
  17. RelativeLayout>  

android:layout_
android:layout_
android:background="@drawable/header_selector" >

android:id="@+id/tvHeader"
android:layout_
android:layout_
android:layout_gravity="start|left"
android:padding="5dp"
android:textColor="@android:color/white"
android:textSize="17sp"
android:text />

因为我们在Header上面只是展现一个日期,所以我们只需要一个TextView即可。

1.2)task_item.xml

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="32dp"  
  5.     android:descendantFocusability="blocksDescendants"  
  6.     android:padding="5dip">  
  7.   
  8.     <ImageView  
  9.         android:padding="5dp"  
  10.         android:layout_centerVertical="true"  
  11.         android:id="@+id/ivComplete"  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="match_parent"  
  14.         android:layout_alignParentLeft="true"  
  15.         android:layout_alignParentTop="true"  
  16.         android:contentDescription="@string/imageview_contentdesc"  
  17.         android:src="@drawable/handdraw_tick"  
  18.         android:visibility="gone" />  
  19.   
  20.     <TextView  
  21.         android:id="@+id/tvTitle"  
  22.         android:layout_width="wrap_content"  
  23.         android:layout_height="match_parent"  
  24.         android:layout_toRightOf="@+id/ivComplete"  
  25.         android:gravity="left|center_vertical"  
  26.         android:padding="5dp"  
  27.         android:textSize="20sp" />  
  28. RelativeLayout>  

android:layout_
android:layout_
android:descendantFocusability="blocksDescendants"
android:padding="5dip">

android:padding="5dp"
android:layout_centerVertical="true"
android:id="@+id/ivComplete"
android:layout_
android:layout_
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:cOntentDescription="@string/imageview_contentdesc"
android:src="@drawable/handdraw_tick"
android:visibility="gone" />

android:id="@+id/tvTitle"
android:layout_
android:layout_
android:layout_toRightOf="@+id/ivComplete"
android:gravity="left|center_vertical"
android:padding="5dp"
android:textSize="20sp" />
在这里面,我们定义了每一个item要展现的布局,跟平常我们经常用的layout其实是一样的,大家接下来自定义的Adapter也就理解了。

2)第二步,跟平常绑定ListView一样,我们也需要自定义一个Adapter,称之为StickyListTaskAdapter。

我们来看一下 StickListTaskAdapter 完整的代码,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class StickListTaskAdapter extends BaseAdapter   
  2.     implements SectionIndexer, StickyListHeadersAdapter{  
  3.       
  4.     private LayoutInflater layoutInflater;  
  5.     private List tasks;  
  6.     private int[] sectionIndices;  
  7.     private String[] sectionHeaders;  
  8.   
  9.     public StickListTaskAdapter(Context context, List tasks) {  
  10.         layoutInflater = LayoutInflater.from(context);  
  11.           
  12.         this.tasks = tasks;  
  13.         sectionIndices = getSectionIndices();  
  14.         sectionHeaders = getSectionHeaders();  
  15.     }  
  16.       
  17.     public void refresh(List tasks){  
  18.         this.tasks = tasks;  
  19.         sectionIndices = getSectionIndices();  
  20.         sectionHeaders = getSectionHeaders();  
  21.         notifyDataSetChanged();  
  22.     }  
  23.       
  24.     private int[] getSectionIndices() {  
  25.         List sectionIndices = new ArrayList();  
  26.         String lastCreateDate = Helper.getFormatDate(tasks.get(0).getCreateTime());  
  27.         sectionIndices.add(0);  
  28.         for (int i = 1; i < tasks.size(); i++) {  
  29.             String createDate = Helper.getFormatDate(tasks.get(i).getCreateTime());  
  30.             if (!createDate.equals(lastCreateDate)) {  
  31.                 lastCreateDate = createDate;  
  32.                 sectionIndices.add(i);  
  33.             }  
  34.         }  
  35.         int[] sections = new int[sectionIndices.size()];  
  36.         for (int i = 0; i < sectionIndices.size(); i++) {  
  37.             sections[i] = sectionIndices.get(i);  
  38.         }  
  39.         return sections;  
  40.     }  
  41.       
  42.     private String[] getSectionHeaders() {  
  43.         String[] sectionHeaders = new String[sectionIndices.length];  
  44.         for (int i = 0; i < sectionIndices.length; i++) {  
  45.             sectionHeaders[i] = Helper.getFormatDate(tasks.get(sectionIndices[i]).getCreateTime());  
  46.         }  
  47.         return sectionHeaders;  
  48.     }  
  49.   
  50.     @Override  
  51.     public int getCount() {  
  52.         return tasks.size();  
  53.     }  
  54.   
  55.     @Override  
  56.     public Object getItem(int position) {  
  57.         return tasks.get(position);  
  58.     }  
  59.   
  60.     @Override  
  61.     public long getItemId(int position) {  
  62.         return tasks.get(position).getId();  
  63.     }  
  64.   
  65.     @Override  
  66.     public View getView(int position, View convertView, ViewGroup parent) {  
  67.         ViewHolder viewHolder;  
  68.         if (convertView == null) {  
  69.             viewHolder = new ViewHolder();  
  70.             convertView = layoutInflater.inflate(R.layout.task_item, null);  
  71.             viewHolder.ivComplete = (ImageView)convertView.findViewById(R.id.ivComplete);  
  72.             viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);  
  73.             viewHolder.tvCreateTime = (TextView) convertView.findViewById(R.id.tvCreateTime);  
  74.             convertView.setTag(viewHolder);  
  75.         } else {  
  76.             viewHolder = (ViewHolder) convertView.getTag();  
  77.         }             
  78.         if("Y".equals(tasks.get(position).getFlagCompleted())){  
  79.             viewHolder.ivComplete.setVisibility(View.VISIBLE);  
  80.             viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCompleteTime()));  
  81.         }else{  
  82.             viewHolder.ivComplete.setVisibility(View.GONE);  
  83.             viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));  
  84.         }  
  85.         viewHolder.tvTitle.setText(tasks.get(position).getTitle());  
  86.           
  87.         return convertView;  
  88.     }  
  89.       
  90.     @Override  
  91.     public View getHeaderView(int position, View convertView, ViewGroup parent) {  
  92.         HeaderViewHolder hvh;  
  93.         if(convertView == null){  
  94.             hvh = new HeaderViewHolder();  
  95.             convertView = layoutInflater.inflate(R.layout.task_header, null);  
  96.             hvh.tvHeader = (TextView) convertView.findViewById(R.id.tvHeader);  
  97.             convertView.setTag(hvh);  
  98.         }else{  
  99.             hvh = (HeaderViewHolder)convertView.getTag();  
  100.         }  
  101.         hvh.tvHeader.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));  
  102.         return convertView;  
  103.     }  
  104.   
  105.     @Override  
  106.     public long getHeaderId(int position) {  
  107.         return Helper.changeStringDateToLong(Helper.getFormatDate(tasks.get(position).getCreateTime()));  
  108.     }  
  109.   
  110.     @Override  
  111.     public Object[] getSections() {  
  112.         // TODO Auto-generated method stub   
  113.         return sectionHeaders;  
  114.     }  
  115.   
  116.     @Override  
  117.     public int getPositionForSection(int sectionIndex) {  
  118.          if (sectionIndex >= sectionIndices.length) {  
  119.              sectionIndex = sectionIndices.length - 1;  
  120.             } else if (sectionIndex < 0) {  
  121.                 sectionIndex = 0;  
  122.             }  
  123.             return sectionIndices[sectionIndex];  
  124.     }  
  125.   
  126.     @Override  
  127.     public int getSectionForPosition(int position) {  
  128.         for (int i = 0; i < sectionIndices.length; i++) {  
  129.             if (position < sectionIndices[i]) {  
  130.                 return i - 1;  
  131.             }  
  132.         }  
  133.         return sectionIndices.length - 1;  
  134.     }  
  135.   
  136.     class ViewHolder {  
  137.         ImageView ivComplete;  
  138.         TextView tvTitle;  
  139.         TextView tvCreateTime;  
  140.     }  
  141.       
  142.     class HeaderViewHolder{  
  143.         TextView tvHeader;  
  144.     }  
  145. }  
public class StickListTaskAdapter extends BaseAdapter 
implements SectionIndexer, StickyListHeadersAdapter{

private LayoutInflater layoutInflater;
private List tasks;
private int[] sectionIndices;
private String[] sectionHeaders;

public StickListTaskAdapter(Context context, List tasks) {
layoutInflater = LayoutInflater.from(context);

this.tasks = tasks;
sectiOnIndices= getSectionIndices();
sectiOnHeaders= getSectionHeaders();
}

public void refresh(List tasks){
this.tasks = tasks;
sectiOnIndices= getSectionIndices();
sectiOnHeaders= getSectionHeaders();
notifyDataSetChanged();
}

private int[] getSectionIndices() {
List sectiOnIndices= new ArrayList();
String lastCreateDate = Helper.getFormatDate(tasks.get(0).getCreateTime());
sectionIndices.add(0);
for (int i = 1; i String createDate = Helper.getFormatDate(tasks.get(i).getCreateTime());
if (!createDate.equals(lastCreateDate)) {
lastCreateDate = createDate;
sectionIndices.add(i);
}
}
int[] sectiOns= new int[sectionIndices.size()];
for (int i = 0; i sections[i] = sectionIndices.get(i);
}
return sections;
}

private String[] getSectionHeaders() {
String[] sectiOnHeaders= new String[sectionIndices.length];
for (int i = 0; i sectionHeaders[i] = Helper.getFormatDate(tasks.get(sectionIndices[i]).getCreateTime());
}
return sectionHeaders;
}

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

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

@Override
public long getItemId(int position) {
return tasks.get(position).getId();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (cOnvertView== null) {
viewHolder = new ViewHolder();
cOnvertView= layoutInflater.inflate(R.layout.task_item, null);
viewHolder.ivComplete = (ImageView)convertView.findViewById(R.id.ivComplete);
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
viewHolder.tvCreateTime = (TextView) convertView.findViewById(R.id.tvCreateTime);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if("Y".equals(tasks.get(position).getFlagCompleted())){
viewHolder.ivComplete.setVisibility(View.VISIBLE);
viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCompleteTime()));
}else{
viewHolder.ivComplete.setVisibility(View.GONE);
viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));
}
viewHolder.tvTitle.setText(tasks.get(position).getTitle());

return convertView;
}

@Override
public View getHeaderView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder hvh;
if(cOnvertView== null){
hvh = new HeaderViewHolder();
cOnvertView= layoutInflater.inflate(R.layout.task_header, null);
hvh.tvHeader = (TextView) convertView.findViewById(R.id.tvHeader);
convertView.setTag(hvh);
}else{
hvh = (HeaderViewHolder)convertView.getTag();
}
hvh.tvHeader.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));
return convertView;
}

@Override
public long getHeaderId(int position) {
return Helper.changeStringDateToLong(Helper.getFormatDate(tasks.get(position).getCreateTime()));
}

@Override
public Object[] getSections() {
// TODO Auto-generated method stub
return sectionHeaders;
}

@Override
public int getPositionForSection(int sectionIndex) {
if (sectionIndex >= sectionIndices.length) {
sectiOnIndex= sectionIndices.length - 1;
} else if (sectionIndex <0) {
sectiOnIndex= 0;
}
return sectionIndices[sectionIndex];
}

@Override
public int getSectionForPosition(int position) {
for (int i = 0; i if (position return i - 1;
}
}
return sectionIndices.length - 1;
}

class ViewHolder {
ImageView ivComplete;
TextView tvTitle;
TextView tvCreateTime;
}

class HeaderViewHolder{
TextView tvHeader;
}
}

首先我们定义了下面两个数组,并且需要在构造的时候初始化它们:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. private int[] sectionIndices;  
  2. private String[] sectionHeaders;  
private int[] sectionIndices;
private String[] sectionHeaders;
通过构造函数,我们可以发现,我们传到这个Adapter的数据源只有一个ArrayList,因为这才是真正的数据,我们分组也是基于这个数据源的。

但是我们要展现Header的,那么Header的数据是从哪里来的呢?所以我们在初始化的时候,就要去获得Header的数据。

大家可以看一下两个getSectionXXX的函数,可以看到在里面做了下面两件事情:

1)sectionIndices数组用来存放每一轮分组的第一个item的位置。

2)sectionHeaders数组用来存放每一个分组要展现的数据,因为能够分到同一组的item,它们肯定有一个相同且可以跟其它section区别开来的值,比如在上面,我是利用create_time来分成不同的组的,所以sectionHeaders存放的只是一个create_time。

不过大家在这里千万要注意:基于某个字段的分组,这个数据源必须是在这个字段上是有序的!

如果不是有序的,那么属于相同分组的数据就会被拆成几段了,而这个分组就没有意义了。

所以如果数据源不是有序的,那么我们在初始化获取分组的时候,也需要先将其变成有序的。

接下来,在我们平常继承BaseAdapter的情况下,我们都要去实现getView等功能,在上面也是一样的,但是我们这个Adapter还必须要实现另外两个接口:

1)StickyListHeadersAdapter

2)SectionIndexer 

我们先来看看StickyListHeaderAdapter的定义:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public interface StickyListHeadersAdapter extends ListAdapter {  
  2.       
  3.     View getHeaderView(int position, View convertView, ViewGroup parent);  
  4.       
  5.     long getHeaderId(int position);  
  6. }  
public interface StickyListHeadersAdapter extends ListAdapter {

View getHeaderView(int position, View convertView, ViewGroup parent);

long getHeaderId(int position);
}

这是开源库提供的接口,因为我们需要添加Header,所以我们必须在Adapter中也返回一个Header的View,这其实跟实现getView是一样的道理的,都挺好理解的。

所以在getHeaderView里面就会用到我们一开始新定义的那个task_header.xml了,同样的,为了实现优化,也会利用一个HeaderViewHolder。

另外一个接口就是SectionIndexer了,它有三个方法要实现,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public interface SectionIndexer {  
  2.   
  3.     Object[] getSections();  
  4.   
  5.     int getPositionForSection(int sectionIndex);  
  6.   
  7.     int getSectionForPosition(int position);  
  8. }  
public interface SectionIndexer {

Object[] getSections();

int getPositionForSection(int sectionIndex);

int getSectionForPosition(int position);
}
看代码的实现,可以发现:

getSections:返回的其实就是Header上面要展示的数据,在这里其实就是sectionHeaders了,存放的是create_time的数据。

getPositionForSection:返回的是这个section数据在List这个基础数据源中的位置,因为section中的数据其实也是从List中获取到的。

getSectionForPosition:则是通过在基础数据源List中的位置找出对应的Section中的数据,原因同上。

那么上面这两个函数的作用在哪?

大家有没有发现,当同一个分组的数据在滚动的时候,最上面的分组并不会变化,只有当滑到其它分组的时候,这个分组才会被新的分组给替换掉。这个效果实现的原理就在这里了,虽然我没有看过源代码,但是我认为,在每一个item滚动的时候,都会找出其对应的分组,然后显示在最上方,如果都是属于同一个分组的话,那么最上面的显示的当然一直都是这个分组对应的Header了。

综上所述,为了实现Sticky和分组的效果,我们就要在原来继承BaseAdapter的基础上再实现多两个接口,并实现对应的逻辑。

那么如何在Activity中使用呢?请看下面的代码:

在xml中定义:

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <se.emilsjolander.stickylistheaders.StickyListHeadersListView  
  2.     android:id="@+id/lvTasks"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="@drawable/todo_bg"  
  6.     android:clipToPadding="false"  
  7.     android:divider="#44FFFFFF"  
  8.     android:dividerHeight="1dp"  
  9.     android:drawSelectorOnTop="true"  
  10.     android:fastScrollEnabled="true"  
  11.     android:overScrollMode="never"  
  12.     android:padding="16dp"  
  13.     android:scrollbarStyle="outsideOverlay" />  
            android:id="@+id/lvTasks"
android:layout_
android:layout_
android:background="@drawable/todo_bg"
android:clipToPadding="false"
android:divider="#44FFFFFF"
android:divider
android:drawSelectorOnTop="true"
android:fastScrollEnabled="true"
android:overScrollMode="never"
android:padding="16dp"
android:scrollbar />
在MainActivity中使用:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. lvTasks = (StickyListHeadersListView) findViewById(R.id.lvTasks);  
  2. taskAdapter = new StickListTaskAdapter(this, tasks);  
  3. lvTasks.setAdapter(taskAdapter);  
  4. lvTasks.setDrawingListUnderStickyHeader(true);  
  5. lvTasks.setAreHeadersSticky(true);  
  6. lvTasks.setOnItemLongClickListener(onItemLongClickListener);  
  7. lvTasks.setOnItemClickListener(onItemClickListener);  
lvTasks = (StickyListHeadersListView) findViewById(R.id.lvTasks);
taskAdapter = new StickListTaskAdapter(this, tasks);
lvTasks.setAdapter(taskAdapter);
lvTasks.setDrawingListUnderStickyHeader(true);
lvTasks.setAreHeadersSticky(true);
lvTasks.setOnItemLongClickListener(onItemLongClickListener);
lvTasks.setOnItemClickListener(onItemClickListener);

而开源库中StickyListHeadersListView还提供了几个接口,可以让我们在Activity中去实现,不过这些就有待大家自己去慢慢学习了。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class StickyListHeadersListView extends FrameLayout {  
  2.   
  3.     public interface OnHeaderClickListener {  
  4.         public void onHeaderClick(StickyListHeadersListView l, View header,  
  5.                                   int itemPosition, long headerId, boolean currentlySticky);  
  6.     }  
  7.   
  8.     /** 
  9.      * Notifies the listener when the sticky headers top offset has changed. 
  10.      */  
  11.     public interface OnStickyHeaderOffsetChangedListener {  
  12.         /** 
  13.          * @param l      The view parent 
  14.          * @param header The currently sticky header being offset. 
  15.          *               This header is not guaranteed to have it's measurements set. 
  16.          *               It is however guaranteed that this view has been measured, 
  17.          *               therefor you should user getMeasured* methods instead of 
  18.          *               get* methods for determining the view's size. 
  19.          * @param offset The amount the sticky header is offset by towards to top of the screen. 
  20.          */  
  21.         public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset);  
  22.     }  
  23.   
  24.     /** 
  25.      * Notifies the listener when the sticky header has been updated 
  26.      */  
  27.     public interface OnStickyHeaderChangedListener {  
  28.         /** 
  29.          * @param l             The view parent 
  30.          * @param header        The new sticky header view. 
  31.          * @param itemPosition  The position of the item within the adapter's data set of 
  32.          *                      the item whose header is now sticky. 
  33.          * @param headerId      The id of the new sticky header. 
  34.          */  
  35.         public void onStickyHeaderChanged(StickyListHeadersListView l, View header,  
  36.                                           int itemPosition, long headerId);  
  37.   
  38.     }  
public class StickyListHeadersListView extends FrameLayout {

public interface OnHeaderClickListener {
public void onHeaderClick(StickyListHeadersListView l, View header,
int itemPosition, long headerId, boolean currentlySticky);
}

/**
* Notifies the listener when the sticky headers top offset has changed.
*/
public interface OnStickyHeaderOffsetChangedListener {
/**
* @param l The view parent
* @param header The currently sticky header being offset.
* This header is not guaranteed to have it's measurements set.
* It is however guaranteed that this view has been measured,
* therefor you should user getMeasured* methods instead of
* get* methods for determining the view's size.
* @param offset The amount the sticky header is offset by towards to top of the screen.
*/
public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset);
}

/**
* Notifies the listener when the sticky header has been updated
*/
public interface OnStickyHeaderChangedListener {
/**
* @param l The view parent
* @param header The new sticky header view.
* @param itemPosition The position of the item within the adapter's data set of
* the item whose header is now sticky.
* @param headerId The id of the new sticky header.
*/
public void onStickyHeaderChanged(StickyListHeadersListView l, View header,
int itemPosition, long headerId);

}

结束。 
http://blog.csdn.net/linmiansheng/article/details/20747775

推荐阅读
author-avatar
man_2_瞳皆哥仔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有