在listview中实现一个可以悬浮在listview顶部且可以被下一个titleBar推动并取代顶部titleBar
来源:互联网 发布:js截取url后面的 编辑:程序博客网 时间:2024/04/24 05:00
1.实现带首字母Title的ListView
重点在MyAdapter的注释
MainActivity.java
public class MainActivity extends Activity {private MyAdapter mAdapter;private ListView mListView;private List<String> mList; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { mList=new ArrayList<String>(); String a=""; for(int i=0;i<26;i++){ a=((char)('A'+i))+""; mList.add(a); mList.add(a); mList.add(a); } mListView=(ListView)findViewById(R.id.lv); mAdapter=new MyAdapter(); mAdapter.update(mList,MainActivity.this); mListView.setAdapter(mAdapter); }}
MyAdapter.java
public class MyAdapter extends BaseAdapter{ private List<String> mList;private Context mContext;public void update(List<String> list,Context context){ mContext=context; Collections.sort(list);//如果是乱序输入的字母,需要排序一下,不然getView的时候会显示很多相同的Title mList=list; this.notifyDataSetChanged();} class ViewHolder{ TextView title; TextView content; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder=new ViewHolder();//听说用convertView是提升ListView性能的好习惯 if(convertView!=null){ holder=(ViewHolder)convertView.getTag(); }else{ convertView=LayoutInflater.from(mContext).inflate(R.layout.item, null); holder.title=(TextView) convertView.findViewById(R.id.item_title); holder.content=(TextView)convertView.findViewById(R.id.item_content); convertView.setTag(holder); } holder.content.setText(mList.get(position));//显示title的逻辑/*看到下面的item.xml文件就可以知道,其实每个listview item都是包含了title和content两个TextView的,只不过title都被隐藏了,我们只要把在首字母相同的分块中的第一个item的title显示出来并setText为该分块的首字母即可*///判断一个item是否是一个分块的第一个item的办法:该item的首字母与它的前一个item首字母不相同 String cur=mList.get(position); String pre=position-1>=0?mList.get(position-1):"";//mList的第一个元素做特殊处理,防止数组越界 if(!(pre.equals(cur))){ holder.title.setVisibility(View.VISIBLE); holder.title.setText(cur); }else{ holder.title.setVisibility(View.GONE); } return convertView; } }
item.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextViewandroid:id="@+id/item_title"android:layout_width="fill_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:textColor="#ffffff"android:textSize="18sp"android:visibility="gone"android:text="A"android:paddingLeft="10dip"android:background="#40E0D0" /> <TextView android:id="@+id/item_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical"android:textColor="#000000"android:textSize="18sp"android:text="content"android:paddingLeft="10dip"android:background="#ffffff" /> /></LinearLayout>
main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListViewandroid:id="@+id/lv" android:layout_width="fill_parent" android:layout_height="fill_parent"android:divider="@null" /></RelativeLayout>
2.实现可以推动的title
实际上,实现可以被推动的Title的原理是新增一个独立的TextView显示在顶端,配合listView的滚动
上图顶部的B-Title,它是独立于listview中所有item 的一个textView,浮在顶部,我们需要的效果是:当下一个title触碰到该TextView的时候,触发推动的效果。
title.xml
<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/header" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:textColor="#ffffff" android:textSize="18sp" android:visibility="gone" android:text="A" android:paddingLeft="10dip" android:background="#40E0D0" />
这时候,我们需要判断,这个独立Title(TextView)的状态:什么时候隐藏(INVISIBLE_STATE=0),什么时候显示(SHOW_STATE=1)以及什么时候触发推动的效果(PUSHING_STATE=2)。这里会用到SectionIndexer的接口,我们改写上面的MyAdapter,让它实现SectionIndexer:
public class MyAdapter extends BaseAdapter implements SectionIndexer,OnScrollListener{ private List<String> mList;private Context context;private String sections[];public void update(List<String> list,Context context){ this.context=context; sections=new String[26];//实验传入的是三个一组的26个字母,所以写成了硬代码26,大家灵活判断一下长度 Collections.sort(list); mList=list; int pos=0; for(int i=0;i<mList.size();i++){ String cur=mList.get(i); String pre=(i-1)>=0?mList.get(i-1):""; if(!(pre.equals(cur))){ sections[pos]=cur; pos++; } } this.notifyDataSetChanged();} @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder=new ViewHolder(); if(convertView!=null){ holder=(ViewHolder)convertView.getTag(); }else{ convertView=LayoutInflater.from(context).inflate(R.layout.list_item, null); holder.title=(TextView) convertView.findViewById(R.id.item_header); holder.content=(TextView)convertView.findViewById(R.id.item_content); convertView.setTag(holder); } holder.content.setText(mList.get(position)); String cur=mList.get(position); String pre=position-1>=0?mList.get(position-1):""; if(!(pre.equals(cur))){ holder.title.setVisibility(View.VISIBLE); holder.title.setText(cur); }else{ holder.title.setVisibility(View.GONE); } return convertView; } class ViewHolder{ TextView title; TextView content; } public int getTitleState(int position) { if (position < 0 || getCount() == 0) { return 0; } int index = getSectionForPosition(position); if(index==-1||index>sections.length){ return 0; } int section = getSectionForPosition(position); int nextSectionPosition = getPositionForSection(section + 1); if (nextSectionPosition != -1 && position == nextSectionPosition - 1) { return 2; } return 1; } @Override public int getPositionForSection(int section) { String sec=sections[section]; int pos=mList.indexOf(sec); return pos; } @Override public int getSectionForPosition(int position) { String a=mList.get(position); for(int i=0;i<sections.length;i++){ if(sections[i]==a){ return i; } } return -1; } @Override public Object[] getSections() { return sections; } public void setTitleText(View mHeader, int firstVisiblePosition) { String title=mList.get(firstVisiblePosition); TextView sectionHeader = (TextView) mHeader; sectionHeader.setText(title); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if(view instanceof PushTitleListView){ System.out.println("onScroll"); ((MyListView)view).titleLayout(firstVisibleItem); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { }}
我也是学习Android不久,很多东西不是很懂,所以大家不要喷我,简单的说说我对Section的理解,如图中的每个字母的区,前面三个A(title不算,一共三个A)构成的分块(区)是一个Section,定为Sections的第0个Section,然后后面三个B也是一个Section,是Sections的第1个Section,后面以此类推。
接下来根据上面约定的基础介绍一下实现SectionIndexer接口会实现下面几个方法:
@Override
public int getPositionForSection(int section) {
String sec=sections[section];
int pos=mList.indexOf(sec);
return pos;
}
getPositionForSection(int section):根据Section在Sections的位置(第几个Section)返回该Section第一个item在全部listview中的位置
例如:根据文章第一张图片,传入0(第0个Section-“A”)它的第一个item在listview的位置是0,传入1(第1个Section-“B”),它的第一个item在listview的位置是3。
@Override
public int getSectionForPosition(int position) {
String a=mList.get(position);
for(int i=0;i<sections.length;i++){
if(sections[i]==a){
return i;
}
}
return -1;
}
getSectionForPosition(int position):根据listview某个item的位置返回该item所在Section在Sections的位置
例如:根据文章第一张图片,传入0,1,2(listview的第0,1,2个item都是A)都会返回0,(A-section在Sections的位置是0),传入 4(listview的第四个item),会返回1(listview的第四个item是“B”,属于Sections中的第1个section),传入10(“D”)返回3。
@Override
public Object[] getSections() {
return sections;
}
上面这个方法不做介绍,实际代码中没有用到,有兴趣的同学可以研究一下官方文档。
有了Section的概念,我们现在可以方便而准确定位每一个Title的位置了(当然还有其他方法,这里是顺带介绍一下SectionIndexer)。接下来介绍一下如何判断独立Title的三个状态:什么时候隐藏(INVISIBLE_STATE=0),什么时候显示(SHOW_STATE=1)以及什么时候触发推动的效果(PUSHING_STATE=2)。
重点是什么时候触发推动效果(PUSHING_STATE=2):
如图,当第一个可视的Item(“B”)(其position由getFirstVisiblePosition()或者onScroll中的参数firstVisibleItem可以得到)的下一个item(“C”)刚好是下一个Section的第一个item,就可以返回PUSHING_STATE=2的状态,提示独立title不断地layout。下面就是获取title状态的方法:
//参数position 传入的是当前可视的第一个item在整个listview中的位置
public int getTitleState(int position) {
if (position < 0 || getCount() == 0) {
return 0;
}
int index = getSectionForPosition(position);
if(index==-1||index>sections.length){
return 0;
}
//当前可视的第一个item所在的section
int section = getSectionForPosition(position);
//下一个section的首位置
int nextSectionPosition = getPositionForSection(section + 1);
//如果下一个section的首位置等于当前可视的第一个item的位置+1,可以返回推动状态(2)了
if (nextSectionPosition != -1 && nextSectionPosition == position + 1) {
return 2;
}
return 1;
}
ps:还是建议大家用Magic Number来描述这三个状态,我是偷懒了,直接返回0,1,2。
有了判断三个状态的方法,我们就可以在listview中调用此方法,根据结果进行独立title的layout
MyListView.java
public class MyListView extends android.widget.ListView { private View mTitle; private boolean visible; private int width; private int height; private MyAdapter mAdapter; public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if(visible){ drawChild(canvas, mTitle, getDrawingTime()); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if(mTitle!=null){ measureChild(mTitle, widthMeasureSpec, heightMeasureSpec); width=mTitle.getMeasuredWidth(); height=mTitle.getMeasuredHeight(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(mTitle!=null){ mTitle.layout(0, 0, width, height); titleLayout(getFirstVisiblePosition()); } } public void setTitle(View view){ mTitle=view; if(mTitle!=null){ setFadingEdgeLength(0); } requestLayout(); } public void titleLayout(int firstVisiblePosition) { if(mTitle==null){ return; } if(mAdapter==null||!(mAdapter instanceof MyAdapter)){ return; } int state=0; state = mAdapter.getTitleState(firstVisiblePosition); switch(state){ case 0: visible=false; break; case 1: if(mTitle.getTop()!=0){ mTitle.layout(0, 0, width, height); } mAdapter.setTitleText(mTitle,firstVisiblePosition); visible=true; break; case 2: View firstView=getChildAt(0); if(firstView!=null){ int bottom=firstView.getBottom(); int headerHeight=mTitle.getHeight(); int top; if(bottom<headerHeight){ top=(bottom-headerHeight); }else{ top=0; } mAdapter.setTitleText(mTitle, firstVisiblePosition); if(mTitle.getTop()!=top){ mTitle.layout(0, top, width, height+top); } visible=true; } break; } } @Override public void setAdapter(ListAdapter adapter) { if(adapter instanceof MyAdapter){ mAdapter=(MyAdapter) adapter; super.setAdapter(adapter); } } }
MainActivity.java
public class MainActivity extends Activity {private MyAdapter mAdapter;private MyListView mListView;private List<String> mList; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { mList=new ArrayList<String>(); String a=""; for(int i=0;i<26;i++){ a=((char)('A'+i))+""; mList.add(a); mList.add(a); mList.add(a); } mListView=(MyListView)findViewById(R.id.lv); mAdapter=new MyAdapter(); mAdapter.update(mList,MainActivity.this); mListView.setTitle(LayoutInflater.from(MainActivity.this).inflate(R.layout.title, mListView, false)); mListView.setAdapter(mAdapter); mListView.setOnScrollListener(mAdapter); }}main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" ><!-- 注意改一下包名,改成你的包名--> <com.lk.test.MyListViewandroid:id="@+id/lv" android:layout_width="fill_parent" android:layout_height="fill_parent"android:divider="@null" /></RelativeLayout>
MyListView中的titleLayout方法:
public void titleLayout(int firstVisiblePosition) {
if(mTitle==null){
return;
}
if(mAdapter==null||!(mAdapter instanceof MyAdapter)){
return;
}
int state=0;
state = mAdapter.getTitleState(firstVisiblePosition);
switch(state){
case 0:
visible=false;
break;
case 1:
if(mTitle.getTop()!=0){
mTitle.layout(0, 0, width, height);
}
mAdapter.setTitleText(mTitle,firstVisiblePosition);
visible=true;
break;
case 2:
View firstView=getChildAt(0);
if(firstView!=null){
int bottom=firstView.getBottom();
int itemHeight=mTitle.getHeight();
int top;
if(bottom<itemHeight){
top=(bottom-itemHeight);
}else{
top=0;
}
mAdapter.setTitleText(mTitle, firstVisiblePosition);
if(mTitle.getTop()!=top){
mTitle.layout(0, top, width, height+top);
}
visible=true;
}
break;
}
}
case 2也就是PUSHING_STATE的时候,调用getChildAt(0)得到当前可视的第一个view,不断的获取它的bottom在Y坐标轴的值bottom(bottom=firstView.getBottom()),如果该值开始小于一个item的高(itemHeight=mTitle.getHeight(),此时注意:独立title的高必须与item中的title高一样,都是wrap_content,否则在推动的时候会有一点误差)的时候,说明第一个item的顶部开始超出y的正轴,准备移动到负轴了。定义一个变量int top=bottom-itemHeight,top的值是负数,也恰好是第一个可视item顶部边界(firstView.getTop())所在Y轴的值,这时候我们令独立Title的位置画的跟第一个可视item的位置一样,调用:mTitle.layout(0,top,width,height+top),其中width和height是在onMeasure的时候确定的,是title的宽度和高度;
case 1的时候只需在mTitle.getTop()!=0的时候调用mTitle.layout(0,0,width,height),将其显示在listview的顶部。
最后在Adapter中重写onScroll监听器,在onScroll的时候不断调用titleLayout方法。
写代码的时候发现两个现象:
(如果在onMeasure,onLayout,dispatchDraw中打log,可以看到执行顺序大致为onMeasure-onMeasure-onLayout-dispatchDraw,其中onMeasure会被调用两次,第一次得到的宽高什么的都是0)
在listview.setOnScrollListener的时候,调用的是:
AbsListview的方法
public void setOnScrollListener(OnScrollListener l) {
mOnScrollListener = l;
invokeOnItemScrollListener();
}
/**
* Notify our scroll listener (if there is one) of a change in scroll state
*/
void invokeOnItemScrollListener() {
if (mFastScroller != null) {
mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
所以onScroll会在这里被触发一次。
其他类中的部分方法不做解释,比较容易看懂。
就先写到这里吧,该回家吃饭了,公司空调都关了。。。。
第一次写博客,代码区不咋会用,乱的不能看了,抱歉
- 在listview中实现一个可以悬浮在listview顶部且可以被下一个titleBar推动并取代顶部titleBar
- ListView顶部悬浮效果
- 自定义顶部TitleBar控件
- 全局实现点击TitleBar滚动到顶部
- 在ListView中实现顶部和底部内容指示器
- 在ListView中实现顶部和底部内容指示器
- 在ListView中实现顶部和底部内容指示器
- 在ListView中实现顶部和底部内容指示器
- 进阶:在ListView中实现顶部和底部的箭头
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- Android 简单实现ListView顶部悬浮效果
- 大整数_int64及long long
- 基于胎压监测传感器的TPMS双向通信系统设计
- c++回调函数
- phpmyadmin导入导出数据库文件最大限制的解决方法
- 观察者
- 在listview中实现一个可以悬浮在listview顶部且可以被下一个titleBar推动并取代顶部titleBar
- jboss 查看浏览器访问server的那个目录
- GetLastErro()函数返回值的含义
- 宏观税负及相关概念
- C语言问题
- php初学者容易犯的几个错误
- 数据库的三级模式和两级映射
- wParam和lParam的区别
- XNA 3D游戏开发入门基本——鼠标选择3D模型(3D物体的拾取)