这两天测试发的bug修得差不多了,有点属于自己的时间,写了个仿美团/京东的ListView联动Demo,现供大家参考参考,如有bug或更好的实现方式,望大家多多指出。
先来看看效果
一、首先我们来看看MainActivity.java 这里有data和view的处理。data为测试用的,view为左边一个ListView以及适配器AdapterLeft和右边RecyclerView和适配器AdapterRight,提醒下RecyclerView别忘了setLayoutManager()。
package com.zyf.linkagelistview;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.zyf.linkagelistview.adapter.AdapterLeft;
import com.zyf.linkagelistview.adapter.AdapterRight;
import com.zyf.linkagelistview.bean.Bean;
import java.util.ArrayList;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private ListView mListViewLeft;
private AdapterLeft mAdapterLeft;
private RecyclerView mListViewRight;
private AdapterRight mAdapterRight;
private ArrayList dataList = new ArrayList<>();
private ArrayList titleList = new ArrayList<>();
private ArrayList titlePosList = new ArrayList<>();
private String mCurTitle = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView(){
mListViewLeft = (ListView) findViewById(R.id.listview_left);
mAdapterLeft = new AdapterLeft(this, titleList);
mListViewLeft.setAdapter(mAdapterLeft);
mListViewLeft.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int pos, long l) {
mAdapterLeft.setSelection(pos);
if (null != titleList && titleList.size()>pos)
mAdapterRight.setSelection(pos);
}
});
mListViewRight = (RecyclerView) findViewById(R.id.listview_right);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mListViewRight.setLayoutManager(linearLayoutManager);
mAdapterRight = new AdapterRight(this, dataList, titlePosList, mListViewRight);
mListViewRight.addItemDecoration(new ItemDecoration(this, dataList, new ItemDecoration.OnDecorationCallback() {
@Override
public String onGroupId(int pos) {
if (dataList.get(pos).getTitle() != null)
return dataList.get(pos).getTitle();
return "-1";
}
@Override
public String onGroupFirstStr(int pos) {
if (dataList.get(pos).getTitle() != null)
return dataList.get(pos).getTitle();
return "";
}
@Override
public void onGroupFirstStr(String title) {
for (int i=0; iif (!mCurTitle.equals(title) && title.equals(titleList.get(i))){
mCurTitle = title;
mAdapterLeft.setSelection(i); // 设置左边ListView选中item
Log.i(TAG, "onGroupFirstStr: i = "+i);
}
}
}
}));
mListViewRight.setAdapter(mAdapterRight);
}
/**
* 数据
*/
private void initData(){
titlePosList.add(0);
for (int i=0; i<5; i++){
Bean bean = new Bean();
bean.setTitle("0");
bean.setText("zzzz");
dataList.add(bean);
}
titleList.add(dataList.get(dataList.size()-1).getTitle());
titlePosList.add(dataList.size());
for (int i=0; i<15; i++){
Bean bean = new Bean();
bean.setTitle("1");
bean.setText("xxxx");
dataList.add(bean);
}
titleList.add(dataList.get(dataList.size()-1).getTitle());
titlePosList.add(dataList.size());
for (int i=0; i<20; i++){
Bean bean = new Bean();
bean.setTitle("2");
bean.setText("cccc");
dataList.add(bean);
}
titleList.add(dataList.get(dataList.size()-1).getTitle());
titlePosList.add(dataList.size());
for (int i=0; i<10; i++){
Bean bean = new Bean();
bean.setTitle("3");
bean.setText("dddd");
dataList.add(bean);
}
titleList.add(dataList.get(dataList.size()-1).getTitle());
mAdapterLeft.notifyDataSetChanged();
mAdapterRight.notifyDataSetChanged();
}
}
其布局也就一个ListView和一个RecyclerView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ListView
android:id="@+id/listview_left"
android:layout_width="100dp"
android:layout_height="match_parent"
android:scrollbars="none"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/listview_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
LinearLayout>
二、数据需要的Bean
package com.zyf.linkagelistview.bean;
/**
* Created by zyf on 2017/5/8.
*/
public class Bean {
private String title;
private String text;
public Bean() {
}
public Bean(String title, String text) {
this.title = title;
this.text = text;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return "Bean{" +
"title='" + title + '\'' +
", text='" + text + '\'' +
'}';
}
}
三、两个适配器
1、左边ListView适配器AdapterLeft.java,方法setSelection(int selection)设置选中其中的item。此处布局就一个TextView就不贴布局代码了。
package com.zyf.linkagelistview.adapter;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.zyf.linkagelistview.R;
import java.util.ArrayList;
/**
* Created by zyf on 2017/5/8.
* 左边ListView适配器
*/
public class AdapterLeft extends BaseAdapter {
private static final String TAG = AdapterLeft.class.getSimpleName();
private Context mContext;
private ArrayList mDataList = new ArrayList<>();
private int mSelection = 0;
public AdapterLeft(Context mContext, ArrayList mDataList) {
this.mContext = mContext;
this.mDataList = mDataList;
}
@Override
public int getCount() {
if (null != mDataList)
return mDataList.size();
return 0;
}
@Override
public Object getItem(int i) {
if (null != mDataList)
return mDataList.get(i);
return null;
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (null == view){
viewHolder = new ViewHolder();
view = LayoutInflater.from(mContext).inflate(R.layout.item_left, null);
viewHolder.textContent = (TextView) view.findViewById(R.id.text_content);
view.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) view.getTag();
}
// 设置被选中的item的字体颜色
if (null != viewHolder.textContent && mSelection == position){
viewHolder.textContent.setTextColor(Color.RED);
}else {
viewHolder.textContent.setTextColor(Color.BLACK);
}
if (null != viewHolder.textContent && null != mDataList && mDataList.size()>0){
viewHolder.textContent.setText(mDataList.get(position));
}else {
Log.i(TAG, "getView: null == mDataList");
}
return view;
}
public int getSelection() {
return mSelection;
}
public void setSelection(int selection) {
mSelection = selection;
notifyDataSetChanged();
}
class ViewHolder{
TextView textHead;
TextView textContent;
}
}
2、右边RecyclerView适配器AdapterRight.java,重点是RecyclerView移动到指定的位置moveToPosition(int n)方法。布局同菜单ListView中的item布局,只有一个TextView就不贴出来了。
package com.zyf.linkagelistview.adapter;
import android.content.Context;
import android.os.SystemClock;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.zyf.linkagelistview.MainActivity;
import com.zyf.linkagelistview.R;
import com.zyf.linkagelistview.bean.Bean;
import java.util.ArrayList;
/**
* Created by zyf on 2017/5/8.
* 右边RecyclerView适配器
*/
public class AdapterRight extends RecyclerView.Adapter {
private static final String TAG = AdapterRight.class.getSimpleName();
private Context mContext;
private ArrayList mDataList = new ArrayList<>();
private ArrayList mTitleIntList = new ArrayList<>();
private RecyclerView mRecyclerView;
private boolean mShouldScroll = false;
public AdapterRight(Context context, ArrayList dataList, ArrayList titleIntList, RecyclerView recyclerView) {
mContext = context;
mDataList = dataList;
mTitleIntList = titleIntList;
mRecyclerView = recyclerView;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_right, null));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder viewHolder = (ViewHolder) holder;
if (null != mDataList && mDataList.size() > 0) {
viewHolder.textContent.setText(mDataList.get(position).getText());
} else {
Log.i(TAG, "getView: null == mDataList");
}
}
@Override
public int getItemCount() {
return mDataList.size();
}
public void setSelection(int pos) {
if (pos < mDataList.size()) {
moveToPosition(pos);
}
}
/**
* 使RecyclerView移动到指定的位置
*/
private void moveToPosition(final int n) {
//先从RecyclerView的LayoutManager中获取第一项和最后一项的Position
int firstItem = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
int lastItem = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
int pos = mTitleIntList.get(n);
mShouldScroll = false;
mRecyclerView.setOnScrollListener(new RecyclerViewListener(n));
//然后区分情况
if (pos <= firstItem) {
//当要置顶的项在当前显示的第一个项的前面时
mRecyclerView.scrollToPosition(pos);
} else if (pos <= lastItem) {
//当要置顶的项已经在屏幕上显示时
int top = mRecyclerView.getChildAt(pos - firstItem).getTop() - 50;
mRecyclerView.scrollBy(0, top);
} else {
//当要置顶的项在当前显示的最后一项的后面时,调用scrollToPosition只会将该项滑动到屏幕上。需要再次滑动到顶部
mRecyclerView.scrollToPosition(pos);
//这里这个变量是用在RecyclerView滚动监听里面的
mShouldScroll = true;
}
}
/**
* 滚动监听
*/
class RecyclerViewListener extends RecyclerView.OnScrollListener{
private int n = 0;
RecyclerViewListener(int n) {
this.n = n;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在这里进行第二次滚动
if (mShouldScroll ){
mShouldScroll = false;
moveToPosition(n);
}
}
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView textContent;
ViewHolder(View itemView) {
super(itemView);
textContent = (TextView) itemView.findViewById(R.id.text_content);
}
}
}
四、RecyclerView吸顶效果的实现所必须要的自定义ItemDecoration 类 这个类需要认真看一下,实现了吸顶效果。通过getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)方法设置预留吸顶的高度,在onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)方法以及onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)方法中进行了坐标的计算,然后使用Canvas以及Paint、TextPaint绘制出吸顶
package com.zyf.linkagelistview;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;
import com.zyf.linkagelistview.bean.Bean;
import java.util.ArrayList;
/**
* Created by zyf on 2017/5/8.
* 实现吸顶功能的RecyclerView
*/
public class ItemDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = ItemDecoration.class.getSimpleName();
private Context mContext;
private ArrayList mDataList = new ArrayList<>();
private OnDecorationCallback mOnDecorationCallback;
private Paint mPaint;
private TextPaint mTextPaint;
private int mTopGap = 50; // 吸顶高(可随意改变)
private int mAlignBottom = 10;
public ItemDecoration(Context context, ArrayList dataList, OnDecorationCallback onDecorationCallback) {
this.mContext = context;
this.mDataList = dataList;
this.mOnDecorationCallback = onDecorationCallback;
Resources resources = mContext.getResources();
mPaint = new Paint();
mPaint.setColor(resources.getColor(R.color.black));
mTextPaint = new TextPaint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setAntiAlias(true); // 去锯齿
mTextPaint.setTextSize(25);
mTextPaint.setTextAlign(Paint.Align.LEFT);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
String groupId = mOnDecorationCallback.onGroupId(pos);
if (groupId.equals("-1"))
return;
if (pos == 0 || isGroupFirstItem(pos)){
outRect.top = mTopGap; // 每组的头部都预留出位置
if (mDataList.get(pos).getTitle().equals(""))
outRect.top = 0;
}else {
outRect.top = 0;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i=0; iint pos = parent.getChildAdapterPosition(view);
String groupId = mOnDecorationCallback.onGroupId(pos);
if (groupId.equals("-1"))
return;
String textLine = mOnDecorationCallback.onGroupFirstStr(pos).toUpperCase();
if (textLine.equals("")){
float top = view.getTop();
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, mPaint);
return;
}else {
if (pos == 0 || isGroupFirstItem(pos)){ // 当前位置为0或为一组中的第一个时,显示顶部
float top = view.getTop() - mTopGap;
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, mPaint);
c.drawText(textLine, left, bottom, mTextPaint);
}
}
}
}
// 在onDraw之后调用,此处做吸顶一直存在的功能
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = state.getItemCount();
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
String preGroupId = "";
String groupId = "-1";
for (int i=0; iint position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = mOnDecorationCallback.onGroupId(position);
if (groupId.equals("-1") || groupId.equals(preGroupId))
continue;
String textLine = mOnDecorationCallback.onGroupFirstStr(position).toUpperCase();
if (TextUtils.isEmpty(textLine))
continue;
int viewBottom = view.getBottom();
// 当view.getTop()
float textY = Math.max(mTopGap, view.getTop());
// 此处实现被后一个title顶出屏幕的效果
if (position + 1 < itemCount){
String nextGroupId = mOnDecorationCallback.onGroupId(position + 1);
// 当后面一组的顶部位置到达当前组吸顶的底部时,将当前组吸顶往上移动(被顶出屏幕)
if (!nextGroupId.equals(groupId) && viewBottom < textY){
textY = viewBottom;
}
}
if (view.getTop() < textY){
mOnDecorationCallback.onGroupFirstStr(textLine);
}
c.drawRect(left, textY - mTopGap, right, textY, mPaint);
c.drawText(textLine, left + 2 * mAlignBottom, textY - mAlignBottom, mTextPaint);
}
}
/**
* 判断是否为组内的第一个item
* @param pos
* @return
*/
private boolean isGroupFirstItem(int pos){
if (pos == 0){
return true;
}else{
String preGroupId = mOnDecorationCallback.onGroupId(pos - 1);
String groupId = mOnDecorationCallback.onGroupId(pos);
if (groupId.equals(preGroupId)){
return false;
}else {
return true;
}
}
}
/**
* 外部接口
*/
interface OnDecorationCallback{
String onGroupId(int pos); // 返回pos位置对应的title
String onGroupFirstStr(int pos); // 返回pos位置对应的title(用于对比title)
void onGroupFirstStr(String title); // 传入的是title
}
}
这样就可以实现简单的ListView联动效果。
五、总结
该联动Demo主要思路是点击左边菜单ListView的item,调用RecyclerView适配器中移动到指定的位置moveToPosition(int n)方法,实现右边RecyclerView滚动到指定位置;滑动右边RecyclerView,调用左边菜单ListView中适配器的setSelection(int pos)方法实现左边联动的功能。 以及RecyclerView的吸顶效果的实现,也是本篇博客的重点。
建议认真理解下RecyclerView的适配器类AdapterRight中moveToPosition(int n)方法,以及实现吸顶功能的自定义ItemDecoration类中吸顶效果的实现方式。大神们如发现有bug或者更好的实现方式,欢迎私信交流!
—————by Jeff—————-
—————分享使我快乐,感谢您的阅读—————