点击左边容器条目,右边列表对应的内容置顶显示,滑动右边的列表,左边容器的对应的标题高亮显示。先看效果:
这个界面是常用于列表的分类展示,两个列表的显示,左边的列表是有标题的,虽然listView能实现这样的分栏显示,但这里有的是一个三方库stickylistheaders
使用很简单直接在app的moudle下引入依赖:compile 'se.emilsjolander:stickylistheaders:2.7.0'
然后就是我们熟悉的找控件设置数据,设置adapter了
private void
initView() {
mSlhlv = (StickyListHeadersListView) findViewById(R.id. slhlv) ;
mLv = (ListView) findViewById(R.id. lv) ;
}
private void initData() {
//0-9的分组
for ( int i = 0 ; i < 10 ; i++) {
HeadData headData = new HeadData() ;
headData. info = i + 1 + "头领" ;
mHeadDatas.add(headData) ;
}
// for (int i = 0; i < 20; i++) {
// Data data = new Data();
// data.info="我是第 :"+i+1+"个条目";
// mDataList.add(data);
// }
//每个头下面添加10条子数据
for ( int i = 0 ; i < mHeadDatas.size() ; i++) {
HeadData headData = mHeadDatas.get(i) ;
for ( int j = 0 ; j < 10 ; j++) {
Data data = new Data() ;
data. info = "我是" + i + "号头领下的:" + j + "号小钻风" ;
data. headid = i ; //此ID是可以是任意的,根据服务器的返回数据,一般是关联的 ID或者某个字符段
data. headindex = i ;
if (j == 0) {
//记录该组的第一条数据的下标
headData. groupFirstIndex = mDataList.size() ;
}
mDataList.add(data) ;
}
}
StickAdapter stickAdapter = new StickAdapter( this) ;
stickAdapter.setCommonData( mDataList , mHeadDatas) ;
Log. d( TAG , "小集合的长度" + mDataList.size()) ;
mSlhlv.setAdapter(stickAdapter) ;
mHeadAdapter = new HeadListAdapter( this , mHeadDatas) ;
mLv.setAdapter( mHeadAdapter) ;
mLv.setOnItemClickListener( this) ;
mSlhlv.setOnScrollListener( this) ;
}
mSlhlv = (StickyListHeadersListView) findViewById(R.id. slhlv) ;
mLv = (ListView) findViewById(R.id. lv) ;
}
private void initData() {
//0-9的分组
for ( int i = 0 ; i < 10 ; i++) {
HeadData headData = new HeadData() ;
headData. info = i + 1 + "头领" ;
mHeadDatas.add(headData) ;
}
// for (int i = 0; i < 20; i++) {
// Data data = new Data();
// data.info="我是第 :"+i+1+"个条目";
// mDataList.add(data);
// }
//每个头下面添加10条子数据
for ( int i = 0 ; i < mHeadDatas.size() ; i++) {
HeadData headData = mHeadDatas.get(i) ;
for ( int j = 0 ; j < 10 ; j++) {
Data data = new Data() ;
data. info = "我是" + i + "号头领下的:" + j + "号小钻风" ;
data. headid = i ; //此ID是可以是任意的,根据服务器的返回数据,一般是关联的 ID或者某个字符段
data. headindex = i ;
if (j == 0) {
//记录该组的第一条数据的下标
headData. groupFirstIndex = mDataList.size() ;
}
mDataList.add(data) ;
}
}
StickAdapter stickAdapter = new StickAdapter( this) ;
stickAdapter.setCommonData( mDataList , mHeadDatas) ;
Log. d( TAG , "小集合的长度" + mDataList.size()) ;
mSlhlv.setAdapter(stickAdapter) ;
mHeadAdapter = new HeadListAdapter( this , mHeadDatas) ;
mLv.setAdapter( mHeadAdapter) ;
mLv.setOnItemClickListener( this) ;
mSlhlv.setOnScrollListener( this) ;
}
思路分析:
一、这里面有两个数据模型,一个Data指的普通条目的数据,一个是HeadData指的是头标题的模型.包括左边的导航标题列表也是用的
mHeadDatas这个数据来源。
二、首先我们看右边的列表StickyListHeadersListView,头条目如果要分到对应的标题栏目下面,那么头目的model中需要有headid这个信息,这个是来判断这个普通条目属于哪个头下面的
三、同时我们在点击左边的列表时如果向让右边的列表对应的内容置顶显示,那么我们HeadData的模型中需要存放该头下面的条目的第一个子条目的position,我们可以根据位置来调用 lv.setSelected(position)'
四、我们要实现滚动右边的列表,左边的列表对应的标题高亮显示,我们在滚动的过程中mSlhlv.setOnScrollListener(this);通过firstVisibleItem拿到第一个可见的条目,所有这里我们可以在普通条目的模型中加入
headindex 这个信息,标识他所在的头条目在
mHeadDatas中的索引。
下面是两个数据Modle:
//普通条目模型
class Data {
String info ; //列表显示信息
int headid ; //根据headid进行分组
int headindex ; //当前条目对应的头数据所在集合的下表
}
//头条目模型
class HeadData {
String info ; //列表显示信息
//点击某个头要知道对应的分组容器的对应的第一个元素的position
int groupFirstIndex ; //改分组下面第一个子元素的position
class Data {
String info ; //列表显示信息
int headid ; //根据headid进行分组
int headindex ; //当前条目对应的头数据所在集合的下表
}
//头条目模型
class HeadData {
String info ; //列表显示信息
//点击某个头要知道对应的分组容器的对应的第一个元素的position
int groupFirstIndex ; //改分组下面第一个子元素的position
}
当我们点击左边的条目是,点击的条目高亮显示,同时右边对应的内容置顶显示
//左边的条目点击
@Override
public void onItemClick(AdapterView<?> parent , View view , int position , long id) {
//联动,当左边的头容器点击某个条目时,让改组信息在右边的容器中置顶
//点击高亮
isUserRoll = false ;
mHeadAdapter.setSelectedPosition(position) ;
int firstIndex = mHeadDatas.get(position). groupFirstIndex ;
//右边的容器滑动到该位置
// mSlhlv.smoothScrollToPosition(firstIndex);
mSlhlv.setSelection(firstIndex) ;
}
@Override
public void onItemClick(AdapterView<?> parent , View view , int position , long id) {
//联动,当左边的头容器点击某个条目时,让改组信息在右边的容器中置顶
//点击高亮
isUserRoll = false ;
mHeadAdapter.setSelectedPosition(position) ;
int firstIndex = mHeadDatas.get(position). groupFirstIndex ;
//右边的容器滑动到该位置
// mSlhlv.smoothScrollToPosition(firstIndex);
mSlhlv.setSelection(firstIndex) ;
}
下面是整个左边列表的HeadAdapter的代码
public class HeadListAdapter
extends BaseAdapter {
private Context mContext ;
private List<HeadData> mHeadDatas ;
private int mSelectedPosition= 0 ;
public HeadListAdapter(Context context , List<HeadData> headDatas) {
mContext=context ;
mHeadDatas=headDatas ;
}
@Override
public int getCount() {
return mHeadDatas== null? 0: mHeadDatas.size() ;
}
@Override
public Object getItem( int position) {
return mHeadDatas== null? null: mHeadDatas.get(position) ;
}
@Override
public long getItemId( int position) {
return position ;
}
@Override
public View getView( int position , View convertView , ViewGroup parent) {
TextView tv = new TextView( mContext) ;
//根据下表拿到头数据
String info = mHeadDatas.get(position). info ;
tv.setText(info) ;
tv.setHeight( 200) ;
tv.setGravity(Gravity. CENTER) ;
tv.setTextSize( 24) ;
tv.setTextColor(Color. BLACK) ;
if (position== mSelectedPosition){
//选中,高亮
tv.setBackgroundColor(Color. WHITE) ;
tv.setTextColor(Color. BLUE) ;
} else {
tv.setBackgroundColor(Color. GRAY) ;
}
return tv ;
}
public void setSelectedPosition( int selectedPosition) {
//优化
if ( mSelectedPosition==selectedPosition){
return ;
}
mSelectedPosition = selectedPosition ;
notifyDataSetChanged() ;
}
private Context mContext ;
private List<HeadData> mHeadDatas ;
private int mSelectedPosition= 0 ;
public HeadListAdapter(Context context , List<HeadData> headDatas) {
mContext=context ;
mHeadDatas=headDatas ;
}
@Override
public int getCount() {
return mHeadDatas== null? 0: mHeadDatas.size() ;
}
@Override
public Object getItem( int position) {
return mHeadDatas== null? null: mHeadDatas.get(position) ;
}
@Override
public long getItemId( int position) {
return position ;
}
@Override
public View getView( int position , View convertView , ViewGroup parent) {
TextView tv = new TextView( mContext) ;
//根据下表拿到头数据
String info = mHeadDatas.get(position). info ;
tv.setText(info) ;
tv.setHeight( 200) ;
tv.setGravity(Gravity. CENTER) ;
tv.setTextSize( 24) ;
tv.setTextColor(Color. BLACK) ;
if (position== mSelectedPosition){
//选中,高亮
tv.setBackgroundColor(Color. WHITE) ;
tv.setTextColor(Color. BLUE) ;
} else {
tv.setBackgroundColor(Color. GRAY) ;
}
return tv ;
}
public void setSelectedPosition( int selectedPosition) {
//优化
if ( mSelectedPosition==selectedPosition){
return ;
}
mSelectedPosition = selectedPosition ;
notifyDataSetChanged() ;
}
当我们滑动右边的列表,我们左边的列表对应的标题高亮显示,滑动监听实现了两个方法,
其中的
onScrollStateChanged方法只有在用户滑动列表操作才会触发,而
onScroll方法在用户滑动操作或点击左边列表时都是会触发的。
//左边容器的滚动的监听
@Override
public void onScrollStateChanged(AbsListView view , int scrollState) {
//当用户执行滚动操作的时候才会触发
isUserRoll = true ;
}
@Override
public void onScroll(AbsListView view , int firstVisibleItem , int visibleItemCount ,
int totalItemCount) {
// 用户执行点击左边容器就会触发
if ( isUserRoll) {
//左边条目高亮显示
Data data = mDataList.get(firstVisibleItem) ;
int headindex = data. headindex ;
mHeadAdapter.setSelectedPosition(headindex) ;
}
}
@Override
public void onScrollStateChanged(AbsListView view , int scrollState) {
//当用户执行滚动操作的时候才会触发
isUserRoll = true ;
}
@Override
public void onScroll(AbsListView view , int firstVisibleItem , int visibleItemCount ,
int totalItemCount) {
// 用户执行点击左边容器就会触发
if ( isUserRoll) {
//左边条目高亮显示
Data data = mDataList.get(firstVisibleItem) ;
int headindex = data. headindex ;
mHeadAdapter.setSelectedPosition(headindex) ;
}
}
下面是右边列表的Adapter,stickylistheaders对应的Adapter需要去实现stickylistheade ,都有注释。
class StickAdapter
extends BaseAdapter
implements StickyListHeadersAdapter {
private List<Data> mCommonData ;
List<HeadData> mHeadDatas ;
private Context mContext ;
public StickAdapter(Context context) {
mContext = context ;
}
//设置数据进来
public void setCommonData(List<Data> commonData , List<HeadData> headDatas) {
mCommonData = commonData ;
mHeadDatas=headDatas ;
}
/***************************************************标题条目*****************************************************/
@Override
public View getHeaderView( int position , View convertView , ViewGroup parent) {
Data data = mCommonData.get(position) ;
//根据下表拿到头数据
HeadData headData = mHeadDatas.get(data. headindex) ;
String info = headData. info ;
TextView tv = new TextView( mContext) ;
tv.setText(info) ;
tv.setTextColor(Color. RED) ;
tv.setBackgroundColor(Color. GRAY) ;
tv.setTextSize( 28) ;
return tv ;
}
//头条目
//依据普通条目获取头di
@Override
public long getHeaderId( int position) {
int headid = mCommonData.get(position). headid ;
return headid ;
}
@Override
public boolean areAllItemsEnabled() {
return false ;
}
/***************************************************普通条目*****************************************************/
//1
@Override
public int getCount() {
return mCommonData== null? 0: mCommonData.size() ;
}
@Override
public Object getItem( int position) {
return mCommonData== null? null: mCommonData.get(position) ;
}
@Override
public long getItemId( int position) {
return position ;
}
@Override
public View getView( int position , View convertView , ViewGroup parent) {
TextView tv = new TextView( mContext) ;
tv.setText( mCommonData.get(position). info) ;
tv.setTextColor(Color. BLUE) ;
tv.setTextSize( 22) ;
return tv ;
}
}
private List<Data> mCommonData ;
List<HeadData> mHeadDatas ;
private Context mContext ;
public StickAdapter(Context context) {
mContext = context ;
}
//设置数据进来
public void setCommonData(List<Data> commonData , List<HeadData> headDatas) {
mCommonData = commonData ;
mHeadDatas=headDatas ;
}
/***************************************************标题条目*****************************************************/
@Override
public View getHeaderView( int position , View convertView , ViewGroup parent) {
Data data = mCommonData.get(position) ;
//根据下表拿到头数据
HeadData headData = mHeadDatas.get(data. headindex) ;
String info = headData. info ;
TextView tv = new TextView( mContext) ;
tv.setText(info) ;
tv.setTextColor(Color. RED) ;
tv.setBackgroundColor(Color. GRAY) ;
tv.setTextSize( 28) ;
return tv ;
}
//头条目
//依据普通条目获取头di
@Override
public long getHeaderId( int position) {
int headid = mCommonData.get(position). headid ;
return headid ;
}
@Override
public boolean areAllItemsEnabled() {
return false ;
}
/***************************************************普通条目*****************************************************/
//1
@Override
public int getCount() {
return mCommonData== null? 0: mCommonData.size() ;
}
@Override
public Object getItem( int position) {
return mCommonData== null? null: mCommonData.get(position) ;
}
@Override
public long getItemId( int position) {
return position ;
}
@Override
public View getView( int position , View convertView , ViewGroup parent) {
TextView tv = new TextView( mContext) ;
tv.setText( mCommonData.get(position). info) ;
tv.setTextColor(Color. BLUE) ;
tv.setTextSize( 22) ;
return tv ;
}
}
,
到这里我们的功能基本实现,有两处需要优化处理:
一、当我们用户是点击左边列表触发的
onScroll,是不需要去刷新左边列表操作的
二、当我们滑动列表的时候回去非常频繁的执行刷新左边列表的操作,对性能消耗大且左边条目可见且已经处于高亮时我们也是不需要去刷新左边列表的。
我们一个定义的Flag来记录是否是用户在滑动(
onScrollStateChanged方法只有在用户滑动列表操作才会触发,而
onScroll方法在用户滑动操作或点击左边列表时都是会触发)
private boolean
isUserRoll =
false
;
//是否是用户的操作滚动
当用户点击条目的时候该值为false,只有当用户滑动时为true.所以我们去刷新左边列表先判断是否是用户滑动触发的
当我们在刷新左边列表是先判断传进来的positon是否就是当前的SelectedPosition,如果不是再去刷新,这样就能减小频繁的刷新操作;
public void
setSelectedPosition(
int selectedPosition) {
//优化
if ( mSelectedPosition==selectedPosition){
return ;
}
mSelectedPosition = selectedPosition ;
notifyDataSetChanged() ;
}
//优化
if ( mSelectedPosition==selectedPosition){
return ;
}
mSelectedPosition = selectedPosition ;
notifyDataSetChanged() ;
}
还有一个问题,当我们滑动右边列表到底部,我们对应的左边列表虽然高亮,当时确实在最底部不可见状态,所有我们需要在
onScroll方法中处理不可见的情况
代码如下:
//判断左边的对应的条目是否可见
//在第一个可见的和最后一个可见的条目中间的条目是可见的,其余的都是不可见的
int firstVisiblePosition = mLv.getFirstVisiblePosition() ;
int lastVisiblePosition = mLv.getLastVisiblePosition() ;
if ( headindex<=firstVisiblePosition || headindex>= lastVisiblePosition) {
Log. d( TAG , "不可见了 +"+headindex) ;
//不可见时
mLv.setSelection(headindex) ;
//在第一个可见的和最后一个可见的条目中间的条目是可见的,其余的都是不可见的
int firstVisiblePosition = mLv.getFirstVisiblePosition() ;
int lastVisiblePosition = mLv.getLastVisiblePosition() ;
if ( headindex<=firstVisiblePosition || headindex>= lastVisiblePosition) {
Log. d( TAG , "不可见了 +"+headindex) ;
//不可见时
mLv.setSelection(headindex) ;
到这里我们已经完成了分类列表的展示,同时实现了左右联动,实际中我们根据后台返回的数据来展示信息,但思路就是这样.主要的就是关于model的处理,标题头的模型中需要有该标题对应的第一个子条目的position才能处理点击左边右边内容置顶。普通条目的模型中需要有对应的标题的索引,才能在滚动过程中,拿到右边的条目位置,做选中处理....