手机QQ好友列表中,组名在滚动的时候会固定在头部,等到这个组全部滚完后,组名也会随着向上滚动。在ListView也可以实现这么的效果。
如上所示:listview中分类显示,每一个类别有一个catalog指示,相当与QQ中的分组,我把这个catalog指示叫做header吧,当这个固定的头部滚出了屏幕外,它的item还在屏幕内,所以需要标识。这时在最上面固定一个header来标识本组,当这个组的item全部滚完后,下一个catalog出来了,这个组的固定header就要被下一个header推出去。这么看不明。。。那就展开QQ的所有分组,感受一下吧~
下面一步一步实现它。
首先,设计数据结构,本来数据只有item,但是分类之后,就要在每一个类别前面加一个catalog,这个catalog看成一个item,但是item类中最好有一个标识,如Tag指示是否为catalog,即header。
首先看整体的布局文件:
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/listview_wrap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</LinearLayout>
<include android:id="@+id/list_header"
layout="@layout/listview_header"/>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_light"
android:orientation="vertical" >
<TextView
android:id="@+id/float_textview"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:text="header"
android:textColor="#FFFFFF"
android:textSize="20sp" />
</RelativeLayout>
list_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="70dp"
android:orientation="vertical"
>
<TextView
android:id="@+id/listitem_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listItem"
android:textSize="20sp"
android:layout_centerVertical="true"
android:textColor="#4d4d4d"
android:layout_marginLeft="10dp"/>
</LinearLayout>
好了,布局完了就开始写代码了。没时间写了,贴上整个代码大家慢慢看吧~
package org.robam.floatlistviewtest;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity implements OnScrollListener {
//测试的数据
private static final String testString = "["
+ "{\"title\":\"图片报告:水价快速上涨势头不变\",\"catalog\":\"1\"},"
+ "{\"title\":\"中国新规要求铁矿石进口商加入CBMX交易平台\",\"catalog\":\"1\"},"
+ "{\"title\":\"早9点简报:重要经济新闻与市场概况一览\",\"catalog\":\"2\"},"
+ "{\"title\":\"凤凰古城资本瓜分秘密:背后是低调亿万富豪\",\"catalog\":\"3\"},"
+ "{\"title\":\"黄金大“劫”案\",\"catalog\":\"3\"},"
+ "{\"title\":\"周小川:经济减速正常 需牺牲增长完成结构调整目标\",\"catalog\":\"4\"},"
+ "{\"title\":\"周小川楼继伟同时表态:日本超宽松政策难自救\",\"catalog\":\"4\"},"
+ "{\"title\":\"周小川楼继伟同时表态:日本超宽松政策难自救\",\"catalog\":\"4\"},"
+ "{\"title\":\"20%个税成房价上升推手 北京有买家承担28万税\",\"catalog\":\"5\"},"
+ "{\"title\":\"燃气公司每立方米亏1元 气价倒挂逼多地上调价格\",\"catalog\":\"6\"},"
+ "{\"title\":\"4月22日国内主要财经媒体头版要闻精选\",\"catalog\":\"6\"},"
+ "{\"title\":\"发改委紧急协调调运煤电油气 全力保障抗震救灾\",\"catalog\":\"7\"},"
+ "{\"title\":\"·北京一季度财政收入增15.6% 房屋销售增长为主因\",\"catalog\":\"7\"},"
+ "{\"title\":\"国土部:去年全国土地出让价款为2.69万亿元\",\"catalog\":\"7\"},"
+ "{\"title\":\"不动产统一登记制度有望年内破冰 推进中频受阻\",\"catalog\":\"8\"},"
+ "{\"title\":\"政府卖地钱一季度增一半 国五条遇土地财政怪圈\",\"catalog\":\"8\"},"
+ "{\"title\":\"十大经济学家解析房价:降价误读 稳价才是真目标\",\"catalog\":\"8\"},"
+ "{\"title\":\"美联储主席称美经济尚不理想 将继续刺激政策\",\"catalog\":\"8\"},"
+ "{\"title\":\"美联储埃文斯:联储不应急于缩减QE\",\"catalog\":\"8\"},"
+ "{\"title\":\"消息称默多克邓文迪离婚协议已接近完成\",\"catalog\":\"9\"},"
+ "{\"title\":\"昨夜今晨国际市场重要财经新闻一览\",\"catalog\":\"9\"},"
+ "{\"title\":\"·巴菲特称股市处于合理区域\",\"catalog\":\"10\"},"
+ "{\"title\":\"美国财长称国会应该考虑债限规则改革\",\"catalog\":\"10\"},"
+ "{\"title\":\"消息称英国监管当局正审查黄金基准价\",\"catalog\":\"10\"},"
+ "{\"title\":\"印度推出女性银行:男人只能存钱 女人可以贷款\",\"catalog\":\"11\"},"
+ "{\"title\":\"波音:787客机问题需6个月时间才能解决\",\"catalog\":\"12\"},"
+ "{\"title\":\"纽约油价19日小幅反弹\",\"catalog\":\"12\"},"
+ "{\"title\":\"诺基亚股东大会批准向微软出售手机业务\",\"catalog\":\"13\"},"
+ "{\"title\":\"连创新高后承压回调 美股周二小幅收低\",\"catalog\":\"14\"},"
+ "{\"title\":\"报告称明年全球企业仅有14%拟聘新员工\",\"catalog\":\"15\"},"
+ "{\"title\":\"摩根大通正式达成130亿美元和解协议\",\"catalog\":\"15\"},"
+ "{\"title\":\"本政府考虑降低必需消费品消费税率\",\"catalog\":\"15\"},"
+ "{\"title\":\"油价周二涨0.3% 收于每桶93.34美元\",\"catalog\":\"15\"}" + "]";
//存储item的list
private List<ItemBeen> theList;
private ListView listview;
// 设置一个static,记录是否要刷新
public static boolean isRefresh = false;
// 浮动层,就是头部
private View floatLayout = null;
//浮动层是否总是显示
private boolean isFloatLayoutShow = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView) findViewById(R.id.listview);
//获得浮动层
floatLayout = findViewById(R.id.list_header);
// 使用前面的测试数据,初始化list列表
try {
JSONArray jsonArray = new JSONArray(testString);
theList = new ArrayList<ItemBeen>();
int size = jsonArray.length();
for (int i = 0; i < size; i++) {
ItemBeen item = new ItemBeen();
JSONObject jsonObject = jsonArray.getJSONObject(i);
item.title = jsonObject.getString("title");
item.catalog = jsonObject.getString("catalog");
theList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
// 在list中写入Tag,来标识是头部。每一个Tag也看成是list中的一个Item,用isTag=true来区分.在不同catalog前面写上一个header
List<ItemBeen> tempList = new ArrayList<ItemBeen>();
String catalog = "";
for (ItemBeen each : theList) {
if (!each.catalog.equals(catalog)) {
// 当这个Item的catalog不等于前面的,就在这个的前面加入一个Tag
ItemBeen header = new ItemBeen();
header.title = "catalog:" + each.catalog;
header.catalog = each.catalog;
header.isTag = true;
tempList.add(header);
catalog = each.catalog;
}
tempList.add(each);
}
theList = tempList;
Log.d("hehe", theList.size() + "");
// 以上写入标签结束
// 设置Adapter
listview.setAdapter(new MyAdapter(this, theList));
// 设置listview滚动监听事件
listview.setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
/**
* 这个onScroll方法在滚动的时候会接收到很多事件,效率嘛。。。自己考虑了
* */
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//排除了初始化的时候visibleitemcount还为o的时候,那就不处理了。如果去掉这个判断,一开始初始化就会抛出事
if (visibleItemCount == 0) {
return;
}
//注意,这个view的值的是可见部分而已,而不是整个listview。
//比如:如果滚到了第十个,即可见的第一个是listview的第十个item,
//那么,getchildat(0)实际上就是listview的第十项。
//刚开始我试图getchildat(firstvisibleItem),还以为这是得到第一个可见的item的view,但不是
//不要吧view中的位置和listview的位置搞乱了。
//获取第一个可见的view
View firstView = view.getChildAt(0);
//第二个可见的view。如果item只有一条。。。好像会抛异常。。。这就留给你们做吧~
View secoundView = view.getChildAt(1);
//当取到的view都不为空的时候才继续哦~
if (firstView != null && secoundView != null && floatLayout != null) {
if (firstVisibleItem == 0 && firstView.getTop() == 0) {
//当第一个是tag并且top在开头,float隐藏
Log.d("hehe", "float hide");
floatLayout.setVisibility(View.INVISIBLE);
isFloatLayoutShow = false;
} else {
floatLayout.setVisibility(View.VISIBLE);
Log.d("hehe", "floatLayout setVisibility VISIBLE ");
}
if (theList.get(firstVisibleItem + 1).isTag
&& firstView.getBottom() < secoundView.getHeight()) {
// 如果第二个是tag,并且第一个的底部里屏幕顶距离小于tag的高度,这时float应该push
Log.d("hehe", "float push");
//设置float层的margin来改变位置。看起来就像被下一个推上去的。
ViewGroup.MarginLayoutParams margin = new ViewGroup.MarginLayoutParams(
floatLayout.getLayoutParams());
margin.setMargins(margin.leftMargin, firstView.getBottom()
- secoundView.getHeight(), margin.rightMargin,
firstView.getBottom());
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
margin);
floatLayout.setLayoutParams(layoutParams);
// 改变Tag的文字,当前Tag的文字
for (int i = firstVisibleItem; i >= 0; i--) {
if (theList.get(i).isTag) {
TextView textView = (TextView) floatLayout
.findViewById(R.id.float_textview);
textView.setText(theList.get(i).title);
break;
}
}
isFloatLayoutShow = false;
} else {
Log.d("hehe", "float should show");
//为什么要这个判断,就是为了避免反复的设置floatlayout的位置。什么情况只要设置一次?
//不被推着走的时候,floatlayout总是会显示的,所以只要做一次设置会原来的位置就可以了
//因为滚动的时候会产生很多onScroll,如果不需要的时候也设置floatLayout的位置,效率很低
if (!isFloatLayoutShow) {
ViewGroup.MarginLayoutParams margin = new ViewGroup.MarginLayoutParams(
floatLayout.getLayoutParams());
Log.d("hehe", floatLayout.getHeight() + "");
margin.setMargins(margin.leftMargin, 0, margin.rightMargin,
floatLayout.getHeight());
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
margin);
floatLayout.setLayoutParams(layoutParams);
// 改变Tag的文字,当前Tag的文字
for (int i = firstVisibleItem; i >= 0; i--) {
if (theList.get(i).isTag) {
TextView textView = (TextView) floatLayout
.findViewById(R.id.float_textview);
textView.setText(theList.get(i).title);
isFloatLayoutShow = true;
break;
}
}
Log.d("hehe", "float show ok");
}
}
}
}
public class ItemBeen {
public String title;
public String catalog;
public boolean isTag = false;
}
public class MyAdapter extends BaseAdapter {
private List<ItemBeen> list;
private LayoutInflater inflater;
private Context mContext;
public MyAdapter(Context context, List<ItemBeen> alist) {
mContext = context;
list = alist;
inflater = getLayoutInflater();
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean isEnabled(int position) {
if (list.get(position).isTag) {
return false;
}
return true;
}
@Override
public int getItemViewType(int position) {
// 如果是Tag,则返回1,不是的就返回0
return list.get(position).isTag ? 1 : 0;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == 1) {
// 是Tag,则是header.
HeaderViewHolder headerHolder;
if (convertView == null
|| convertView.findViewById(R.id.float_textview) == null) {
// 如果converView是空或者converView原来不是header的view,都要从新new view
convertView = inflater.inflate(R.layout.listview_header,
null);
headerHolder = new HeaderViewHolder();
headerHolder.title = (TextView) convertView
.findViewById(R.id.float_textview);
convertView.setTag(headerHolder);
} else {
headerHolder = (HeaderViewHolder) convertView.getTag();
}
headerHolder.title.setText(list.get(position).title);
} else {
// 是一般的Item
ItemViewHolder itemHolder;
if (convertView == null
|| convertView.findViewById(R.id.listitem_textview) == null) {
// 如果converView是空或者converView原来不是header的view,都要从新new view
convertView = inflater.inflate(R.layout.list_item, null);
itemHolder = new ItemViewHolder();
itemHolder.title = (TextView) convertView
.findViewById(R.id.listitem_textview);
convertView.setTag(itemHolder);
} else {
itemHolder = (ItemViewHolder) convertView.getTag();
}
itemHolder.title.setText(list.get(position).title);
}
return convertView;
}
}
class HeaderViewHolder {
TextView title;
}
class ItemViewHolder {
TextView title;
}
}