http://www.jianshu.com/p/d9e75ac904e7
双联动ListView在很多APP中都有应用,美团外卖、糯米外卖、京东分类等都有使用。
双ListView分为左侧ListView和右侧ListView,通过左侧ListView的点击来定位右侧ListView的位置,通过右侧ListView的滑动来定位左侧ListView的选中位置。左侧ListView我们直接使用原生ListView即可,右侧ListView由于需要头部标题和滑动监听,我们自己定义一个ListView,来设置下滑动事件的监听和头部标题。
package cn.studyou.doublelistviewlinkage.View;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.HeaderViewListAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import cn.studyou.doublelistviewlinkage.Adapter.SectionedBaseAdapter;
public class PinnedHeaderListView extends ListView implements OnScrollListener {
private OnScrollListener mOnScrollListener;
public static interface PinnedSectionedHeaderAdapter {
public boolean isSectionHeader(int position);
public int getSectionForPosition(int position);
public View getSectionHeaderView(int section, View convertView, ViewGroup parent);
public int getSectionHeaderViewType(int section);
public int getCount();
}
private PinnedSectionedHeaderAdapter mAdapter;
private View mCurrentHeader;
private int mCurrentHeaderViewType = 0;
private float mHeaderOffset;
private boolean mShouldPin = true;
private int mCurrentSection = 0;
private int mWidthMode;
private int mHeightMode;
public PinnedHeaderListView(Context context) {
super(context);
super.setOnScrollListener(this);
}
public PinnedHeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnScrollListener(this);
}
public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setOnScrollListener(this);
}
public void setPinHeaders(boolean shouldPin) {
mShouldPin = shouldPin;
}
@Override
public void setAdapter(ListAdapter adapter) {
mCurrentHeader = null;
mAdapter = (PinnedSectionedHeaderAdapter) adapter;
super.setAdapter(adapter);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < getHeaderViewsCount())) {
mCurrentHeader = null;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
View header = getChildAt(i);
if (header != null) {
header.setVisibility(VISIBLE);
}
}
return;
}
firstVisibleItem -= getHeaderViewsCount();
int section = mAdapter.getSectionForPosition(firstVisibleItem);
int viewType = mAdapter.getSectionHeaderViewType(section);
mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null : mCurrentHeader);
ensurePinnedHeaderLayout(mCurrentHeader);
mCurrentHeaderViewType = viewType;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
if (mAdapter.isSectionHeader(i)) {
View header = getChildAt(i - firstVisibleItem);
float headerTop = header.getTop();
float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight();
header.setVisibility(VISIBLE);
if (pinnedHeaderHeight >= headerTop && headerTop > 0) {
mHeaderOffset = headerTop - header.getHeight();
} else if (headerTop <= 0) {
header.setVisibility(INVISIBLE);
}
}
}
invalidate();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
private View getSectionHeaderView(int section, View oldView) {
boolean shouldLayout = section != mCurrentSection || oldView == null;
View view = mAdapter.getSectionHeaderView(section, oldView, this);
if (shouldLayout) {
// a new section, thus a new header. We should lay it out again
ensurePinnedHeaderLayout(view);
mCurrentSection = section;
}
return view;
}
private void ensurePinnedHeaderLayout(View header) {
if (header.isLayoutRequested()) {
int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), mWidthMode);
int heightSpec;
ViewGroup.LayoutParams layoutParams = header.getLayoutParams();
if (layoutParams != null && layoutParams.height > 0) {
heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
} else {
heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
header.measure(widthSpec, heightSpec);
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mAdapter == null || !mShouldPin || mCurrentHeader == null)
return;
int saveCount = canvas.save();
canvas.translate(0, mHeaderOffset);
canvas.clipRect(0, 0, getWidth(), mCurrentHeader.getMeasuredHeight()); // needed
mCurrentHeader.draw(canvas);
canvas.restoreToCount(saveCount);
}
@Override
public void setOnScrollListener(OnScrollListener l) {
mOnScrollListener = l;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthMode = MeasureSpec.getMode(widthMeasureSpec);
mHeightMode = MeasureSpec.getMode(heightMeasureSpec);
}
public void setOnItemClickListener(OnItemClickListener listener) {
super.setOnItemClickListener(listener);
}
public static abstract class OnItemClickListener implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView> adapterView, View view, int rawPosition, long id) {
SectionedBaseAdapter adapter;
if (adapterView.getAdapter().getClass().equals(HeaderViewListAdapter.class)) {
HeaderViewListAdapter wrapperAdapter = (HeaderViewListAdapter) adapterView.getAdapter();
adapter = (SectionedBaseAdapter) wrapperAdapter.getWrappedAdapter();
} else {
adapter = (SectionedBaseAdapter) adapterView.getAdapter();
}
int section = adapter.getSectionForPosition(rawPosition);
int position = adapter.getPositionInSectionForPosition(rawPosition);
if (position == -1) {
onSectionClick(adapterView, view, section, id);
} else {
onItemClick(adapterView, view, section, position, id);
}
}
public abstract void onItemClick(AdapterView> adapterView, View view, int section, int position, long id);
public abstract void onSectionClick(AdapterView> adapterView, View view, int section, long id);
}
}
package cn.studyou.doublelistviewlinkage.Adapter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import cn.studyou.doublelistviewlinkage.View.PinnedHeaderListView;
public abstract class SectionedBaseAdapter extends BaseAdapter implements PinnedHeaderListView.PinnedSectionedHeaderAdapter {
private static int HEADER_VIEW_TYPE = 0;
private static int ITEM_VIEW_TYPE = 0;
private SparseArray mSectionPositionCache;
private SparseArray mSectionCache;
private SparseArray mSectionCountCache;
private int mCount;
private int mSectionCount;
public SectionedBaseAdapter() {
super();
mSectionCache = new SparseArray();
mSectionPositionCache = new SparseArray();
mSectionCountCache = new SparseArray();
mCount = -1;
mSectionCount = -1;
}
@Override
public void notifyDataSetChanged() {
mSectionCache.clear();
mSectionPositionCache.clear();
mSectionCountCache.clear();
mCount = -1;
mSectionCount = -1;
super.notifyDataSetChanged();
}
@Override
public void notifyDataSetInvalidated() {
mSectionCache.clear();
mSectionPositionCache.clear();
mSectionCountCache.clear();
mCount = -1;
mSectionCount = -1;
super.notifyDataSetInvalidated();
}
@Override
public final int getCount() {
if (mCount >= 0) {
return mCount;
}
int count = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
count += internalGetCountForSection(i);
count++; // for the header view
}
mCount = count;
return count;
}
@Override
public final Object getItem(int position) {
return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final long getItemId(int position) {
return getItemId(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
if (isSectionHeader(position)) {
return getSectionHeaderView(getSectionForPosition(position), convertView, parent);
}
return getItemView(getSectionForPosition(position), getPositionInSectionForPosition(position), convertView, parent);
}
@Override
public final int getItemViewType(int position) {
if (isSectionHeader(position)) {
return getItemViewTypeCount() + getSectionHeaderViewType(getSectionForPosition(position));
}
return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final int getViewTypeCount() {
return getItemViewTypeCount() + getSectionHeaderViewTypeCount();
}
public final int getSectionForPosition(int position) {
// first try to retrieve values from cache
Integer cachedSection = mSectionCache.get(position);
if (cachedSection != null) {
return cachedSection;
}
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount + 1;
if (position >= sectionStart && position < sectionEnd) {
mSectionCache.put(position, i);
return i;
}
sectionStart = sectionEnd;
}
return 0;
}
public int getPositionInSectionForPosition(int position) {
// first try to retrieve values from cache
Integer cachedPosition = mSectionPositionCache.get(position);
if (cachedPosition != null) {
return cachedPosition;
}
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount + 1;
if (position >= sectionStart && position < sectionEnd) {
int positionInSection = position - sectionStart - 1;
mSectionPositionCache.put(position, positionInSection);
return positionInSection;
}
sectionStart = sectionEnd;
}
return 0;
}
public final boolean isSectionHeader(int position) {
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
if (position == sectionStart) {
return true;
} else if (position < sectionStart) {
return false;
}
sectionStart += internalGetCountForSection(i) + 1;
}
return false;
}
public int getItemViewType(int section, int position) {
return ITEM_VIEW_TYPE;
}
public int getItemViewTypeCount() {
return 1;
}
public int getSectionHeaderViewType(int section) {
return HEADER_VIEW_TYPE;
}
public int getSectionHeaderViewTypeCount() {
return 1;
}
public abstract Object getItem(int section, int position);
public abstract long getItemId(int section, int position);
public abstract int getSectionCount();
public abstract int getCountForSection(int section);
public abstract View getItemView(int section, int position, View convertView, ViewGroup parent);
public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent);
private int internalGetCountForSection(int section) {
Integer cachedSectionCount = mSectionCountCache.get(section);
if (cachedSectionCount != null) {
return cachedSectionCount;
}
int sectionCount = getCountForSection(section);
mSectionCountCache.put(section, sectionCount);
return sectionCount;
}
private int internalGetSectionCount() {
if (mSectionCount >= 0) {
return mSectionCount;
}
mSectionCount = getSectionCount();
return mSectionCount;
}
}
package cn.studyou.doublelistviewlinkage.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;
import butterknife.Bind;
import butterknife.ButterKnife;
import cn.studyou.doublelistviewlinkage.Adapter.LeftListAdapter;
import cn.studyou.doublelistviewlinkage.Adapter.MainSectionedAdapter;
import cn.studyou.doublelistviewlinkage.R;
import cn.studyou.doublelistviewlinkage.View.PinnedHeaderListView;
public class MainActivity extends AppCompatActivity {
@Bind(R.id.left_listview)
ListView leftListview;
@Bind(R.id.pinnedListView)
PinnedHeaderListView pinnedListView;
private boolean isScroll = true;
private LeftListAdapter adapter;
private String[] leftStr = new String[]{"面食类", "盖饭", "寿司", "烧烤", "酒水", "凉菜", "小吃", "粥", "休闲"};
private boolean[] flagArray = {true, false, false, false, false, false, false, false, false};
private String[][] rightStr = new String[][]{{"热干面", "臊子面", "烩面"},
{"番茄鸡蛋", "红烧排骨", "农家小炒肉"},
{"芝士", "丑小丫", "金枪鱼"}, {"羊肉串", "烤鸡翅", "烤羊排"}, {"长城干红", "燕京鲜啤", "青岛鲜啤"},
{"拌粉丝", "大拌菜", "菠菜花生"}, {"小食组", "紫薯"},
{"小米粥", "大米粥", "南瓜粥", "玉米粥", "紫米粥"}, {"儿童小汽车", "悠悠球", "熊大", " 熊二", "光头强"}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
pinnedListView = (PinnedHeaderListView) findViewById(R.id.pinnedListView);
final MainSectionedAdapter sectionedAdapter = new MainSectionedAdapter(this, leftStr, rightStr);
pinnedListView.setAdapter(sectionedAdapter);
adapter = new LeftListAdapter(this, leftStr, flagArray);
leftListview.setAdapter(adapter);
leftListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> arg0, View view, int position, long arg3) {
isScroll = false;
for (int i = 0; i < leftStr.length; i++) {
if (i == position) {
flagArray[i] = true;
} else {
flagArray[i] = false;
}
}
adapter.notifyDataSetChanged();
int rightSection = 0;
for (int i = 0; i < position; i++) {
rightSection += sectionedAdapter.getCountForSection(i) + 1;
}
pinnedListView.setSelection(rightSection);
}
});
pinnedListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView arg0, int scrollState) {
switch (scrollState) {
// 当不滚动时
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
// 判断滚动到底部
if (pinnedListView.getLastVisiblePosition() == (pinnedListView.getCount() - 1)) {
leftListview.setSelection(ListView.FOCUS_DOWN);
}
// 判断滚动到顶部
if (pinnedListView.getFirstVisiblePosition() == 0) {
leftListview.setSelection(0);
}
break;
}
}
int y = 0;
int x = 0;
int z = 0;
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (isScroll) {
for (int i = 0; i < rightStr.length; i++) {
if (i == sectionedAdapter.getSectionForPosition(pinnedListView.getFirstVisiblePosition())) {
flagArray[i] = true;
x = i;
} else {
flagArray[i] = false;
}
}
if (x != y) {
adapter.notifyDataSetChanged();
y = x;
//左侧ListView滚动到最后位置
if (y == leftListview.getLastVisiblePosition()) {
leftListview.setSelection(z);
}
//左侧ListView滚动到第一个位置
if (x == leftListview.getFirstVisiblePosition()) {
leftListview.setSelection(z);
}
if (firstVisibleItem + visibleItemCount == totalItemCount - 1) {
leftListview.setSelection(ListView.FOCUS_DOWN);
}
}
} else {
isScroll = true;
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="horizontal">
<ListView
android:id="@+id/left_listview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:scrollbars="none"
android:divider="@null">ListView>
<cn.studyou.doublelistviewlinkage.View.PinnedHeaderListView
android:id="@+id/pinnedListView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/dp10"
android:layout_weight="5"
android:background="@android:color/white">cn.studyou.doublelistviewlinkage.View.PinnedHeaderListView>
LinearLayout>
package cn.studyou.doublelistviewlinkage.Adapter;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import cn.studyou.doublelistviewlinkage.R;
/**
* 基本功能:左侧Adapter
* 创建:王杰
* 创建时间:16/4/14
* 邮箱:w489657152@gmail.com
*/public class LeftListAdapter extends BaseAdapter {
private String[] leftStr;
boolean[] flagArray;
private Context context;
public LeftListAdapter(Context context, String[] leftStr, boolean[] flagArray) {
this.leftStr = leftStr;
this.context = context;
this.flagArray = flagArray;
}
@Override
public int getCount() {
return leftStr.length;
}
@Override
public Object getItem(int arg0) {
return leftStr[arg0];
}
@Override
public long getItemId(int arg0) {
return arg0;
}
@Override
public View getView(int arg0, View arg1, ViewGroup arg2) {
Holder holder = null;
if (arg1 == null) {
holder = new Holder();
arg1 = LayoutInflater.from(context).inflate(R.layout.left_list_item, null);
holder.left_list_item = (TextView) arg1.findViewById(R.id.left_list_item);
arg1.setTag(holder);
} else {
holder = (Holder) arg1.getTag();
}
holder.updataView(arg0);
return arg1;
}
private class Holder {
private TextView left_list_item;
public void updataView(final int position) {
left_list_item.setText(leftStr[position]);
if (flagArray[position]) {
left_list_item.setBackgroundColor(Color.rgb(255, 255, 255));
} else {
left_list_item.setBackgroundColor(Color.TRANSPARENT);
}
}
}
}
package cn.studyou.doublelistviewlinkage.Adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import cn.studyou.doublelistviewlinkage.R;
/**
* 基本功能:右侧Adapter
* 创建:王杰
* 创建时间:16/4/14
* 邮箱:w489657152@gmail.com
*/public class MainSectionedAdapter extends SectionedBaseAdapter {
private Context mContext;
private String[] leftStr;
private String[][] rightStr;
public MainSectionedAdapter(Context context, String[] leftStr, String[][] rightStr) {
this.mContext = context;
this.leftStr = leftStr;
this.rightStr = rightStr;
}
@Override
public Object getItem(int section, int position) {
return rightStr[section][position];
}
@Override
public long getItemId(int section, int position) {
return position;
}
@Override
public int getSectionCount() {
return leftStr.length;
}
@Override
public int getCountForSection(int section) {
return rightStr[section].length;
}
@Override
public View getItemView(final int section, final int position, View convertView, ViewGroup parent) {
RelativeLayout layout = null;
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (RelativeLayout) inflator.inflate(R.layout.right_list_item, null);
} else {
layout = (RelativeLayout) convertView;
}
((TextView) layout.findViewById(R.id.textItem)).setText(rightStr[section][position]);
layout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Toast.makeText(mContext, rightStr[section][position], Toast.LENGTH_SHORT).show();
}
});
return layout;
}
@Override
public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
LinearLayout layout = null;
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (LinearLayout) inflator.inflate(R.layout.header_item, null);
} else {
layout = (LinearLayout) convertView;
}
layout.setClickable(false);
((TextView) layout.findViewById(R.id.textItem)).setText(leftStr[section]);
return layout;
}
}
header_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/gray_light"
android:orientation="horizontal">
<TextView
android:id="@+id/textItem"
android:layout_marginLeft="@dimen/dp10"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp30"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp6"
android:textColor="@color/black" />LinearLayout>
left_list_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray_light"
android:orientation="vertical" >
<TextView
android:id="@+id/left_list_item"
android:layout_width="match_parent"
android:layout_height="@dimen/dp60"
android:padding="@dimen/dp12"
android:gravity="center_vertical"
android:textColor="@color/black"/>
LinearLayout>
right_list_item.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:padding="@dimen/dp6">
<ImageView
android:id="@+id/imageItem"
android:layout_width="@dimen/dp50"
android:layout_height="@dimen/dp50"
android:scaleType="fitXY"
android:src="@drawable/pic"/>
<TextView
android:id="@+id/textItem"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp50"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp6"
android:layout_toRightOf="@+id/imageItem"
android:textColor="@color/black" />RelativeLayout>