一步一步做ListView滚动固定头部并且头部会变化哦

手机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>


listview_header.xml    --- 这是header的布局

<?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;
	}

}





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值