没有效果图的示例简直就是扯淡
通过两个RecyclerView实现左右联动,吸附的标题通过自定义分割线view实现
源码在最下面👇
废话不多说,直接看代码吧。
Mainactivity.class
package com.cc.test;
import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.cc.test.adapter.LeftAdapter;
import com.cc.test.adapter.RightAdapter;
import com.cc.test.listener.RightItemCheckListener;
import com.cc.test.model.RightBean;
import com.cc.test.model.SortBean;
import com.cc.test.view.ItemHeaderDecoration;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 主界面
* 14点57分
*
* @author cc
*/
public class MainActivity extends AppCompatActivity implements RightItemCheckListener {
//左边的rv列表和适配器
private RecyclerView mRvLeft;
private LeftAdapter leftAdapter;
private LinearLayoutManager mLinearLayoutManager;
//右边的rv列表和适配器
private RecyclerView mRvRight;
private RightAdapter rightAdapter;
private GridLayoutManager mManager;
//总数据源
private List<SortBean> mSortBean = new ArrayList<>();
//右边的数据源
private List<RightBean> rightBeanList = new ArrayList<>();
//头部吸附自定义分割线
private ItemHeaderDecoration mDecoration;
private boolean move = false;
//标量
private int mIndex = 0;
//点击左边某一个具体的item的位置
private int targetPosition;
//是否可移动
private boolean isMoved;
/**
* 开始
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//加载默认数据
getDefaultData();
//初始化控件
initLeftView();
initRightView();
}
/**
* 获取默认数据
*/
private void getDefaultData() {
//获取asset目录下的资源文件
String assetsData = getAssetsData("data.json");
Gson gson = new Gson();
//mSortBean就是要使用的集合啦
mSortBean = gson.fromJson(assetsData, new TypeToken<List<SortBean>>() {
}.getType());
}
/**
* 初始化左边的rv及绑定适配器
*/
private void initLeftView() {
mRvLeft = (RecyclerView) findViewById(R.id.rv_left);
mLinearLayoutManager = new LinearLayoutManager(this);
mRvLeft.setLayoutManager(mLinearLayoutManager);
DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
mRvLeft.addItemDecoration(decoration);
//左边的item要填充的内容啦
List<String> leftTitle = new ArrayList<>();
//初始化左侧列表数据
for (int i = 0; i < mSortBean.size(); i++) {
leftTitle.add(mSortBean.get(i).getName());
}
//左边的adapter开始实现并处理item的点击事件
leftAdapter = new LeftAdapter(this, leftTitle, (id, position) -> {
//可以移动啦
isMoved = true;
//目标的下标
targetPosition = position;
//去设置
setChecked(position, true);
});
//绑定适配器
mRvLeft.setAdapter(leftAdapter);
}
/**
* 创建右边的布局
*/
private void initRightView() {
//获取右边的控件
mRvRight = (RecyclerView) findViewById(R.id.rv_right);
//监听右边的滑动事件
mRvRight.addOnScrollListener(new RecyclerViewListener());
//创建item布局管理器
mManager = new GridLayoutManager(this, 3);
//通过isTitle的标志来判断是否是头部吸附title
mManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return rightBeanList.get(position).isTitle() ? 3 : 1;
}
});
//设置布局管理
mRvRight.setLayoutManager(mManager);
//点击事件
rightAdapter = new RightAdapter(this, rightBeanList, (id, position) -> {
String content = "";
switch (id) {
case R.id.root:
content = "title";
break;
case R.id.content:
content = "content";
break;
}
Snackbar snackbar = Snackbar.make(mRvRight, "当前点击的是" + content + ":" + rightBeanList.get(position).getName(), Snackbar.LENGTH_SHORT);
View mView = snackbar.getView();
mView.setBackgroundColor(Color.BLUE);
TextView text = mView.findViewById(android.support.design.R.id.snackbar_text);
text.setTextColor(Color.WHITE);
text.setTextSize(25);
snackbar.show();
});
//绑定适配器
mRvRight.setAdapter(rightAdapter);
//创建一个自定义分割线,给右边的列表布局添加上
mDecoration = new ItemHeaderDecoration(this, rightBeanList);
mRvRight.addItemDecoration(mDecoration);
//分割线点击事件
mDecoration.setCheckListener(this);
//循环遍历数据源
for (int i = 0; i < mSortBean.size(); i++) {
//将一级数据添加为头部数据(就左边的列表数据)
RightBean head = new RightBean(mSortBean.get(i).getName());
//头部设置为true
head.setTitle(true);
head.setTitleName(mSortBean.get(i).getName());
//这里的tag不能为空,不能重复,是滑动对比用的
head.setTag(String.valueOf(i));
//添加到右边的数据源集合里面去
rightBeanList.add(head);
//开始遍历二级数据,真正在右边item加载的数据
List<SortBean.CategoryTwoArrayBean> categoryTwoArray = mSortBean.get(i).getCategoryTwoArray();
//遍历
for (int j = 0; j < categoryTwoArray.size(); j++) {
//遍历每一个并添加到右边的数据源里面
RightBean body = new RightBean(categoryTwoArray.get(j).getName());
body.setTag(String.valueOf(i));
String name = mSortBean.get(i).getName();
body.setTitleName(name);
rightBeanList.add(body);
}
}
//刷新
rightAdapter.notifyDataSetChanged();
mDecoration.setData(rightBeanList);
}
/**
* 右边列表的滑动事件
*/
private class RecyclerViewListener extends RecyclerView.OnScrollListener {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (move && newState == RecyclerView.SCROLL_STATE_IDLE) {
move = false;
int n = mIndex - mManager.findFirstVisibleItemPosition();
if (0 <= n && n < mRvRight.getChildCount()) {
int top = mRvRight.getChildAt(n).getTop();
mRvRight.smoothScrollBy(0, top);
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (move) {
move = false;
int n = mIndex - mManager.findFirstVisibleItemPosition();
if (0 <= n && n < mRvRight.getChildCount()) {
int top = mRvRight.getChildAt(n).getTop();
mRvRight.scrollBy(0, top);
}
}
}
}
/**
* 选中的点击事件
*
* @param position 当前下标
* @param isLeft 是否是左边的
*/
private void setChecked(int position, boolean isLeft) {
//左边
if (isLeft) {
leftAdapter.setCheckedPosition(position);
//此处的位置需要根据每个分类的集合来进行计算
int count = 0;
for (int i = 0; i < position; i++) {
count += mSortBean.get(i).getCategoryTwoArray().size();
}
count += position;
mIndex = count;
mRvRight.stopScroll();
smoothMoveToPosition(count);
ItemHeaderDecoration.setCurrentTag(String.valueOf(targetPosition));//凡是点击左边,将左边点击的位置作为当前的tag
} else {
//右边
if (isMoved) {
isMoved = false;
} else {
leftAdapter.setCheckedPosition(position);
}
ItemHeaderDecoration.setCurrentTag(String.valueOf(position));//如果是滑动右边联动左边,则按照右边传过来的位置作为tag
}
//移动到中间
moveToCenter(position);
}
/**
* 滑动到某一个位置
*
* @param n
*/
private void smoothMoveToPosition(int n) {
int firstItem = mManager.findFirstVisibleItemPosition();
int lastItem = mManager.findLastVisibleItemPosition();
if (n <= firstItem) {
mRvRight.scrollToPosition(n);
} else if (n <= lastItem) {
int top = mRvRight.getChildAt(n - firstItem).getTop();
mRvRight.scrollBy(0, top);
} else {
mRvRight.scrollToPosition(n);
move = true;
}
}
/**
* 将当前左边选中的item居中
*
* @param position
*/
private void moveToCenter(int position) {
//将点击的position转换为当前屏幕上可见的item的位置以便于计算距离顶部的高度,从而进行移动居中
View childAt = mRvLeft.getChildAt(position - mLinearLayoutManager.findFirstVisibleItemPosition());
if (childAt != null) {
int y = (childAt.getTop() - mRvLeft.getHeight() / 2);
mRvLeft.smoothScrollBy(0, y);
}
}
/**
* 右边item事件
*
* @param position
* @param isScroll
*/
@Override
public void check(int position, boolean isScroll) {
setChecked(position, isScroll);
}
/**
* 从资源文件中获取分类json
*
* @param path
* @return
*/
private String getAssetsData(String path) {
String result = "";
try {
//获取输入流
InputStream mAssets = getAssets().open(path);
//获取文件的字节数
int length = mAssets.available();
//创建byte数组
byte[] buffer = new byte[length];
//将文件中的数据写入到字节数组中
mAssets.read(buffer);
mAssets.close();
result = new String(buffer);
return result;
} catch (IOException e) {
e.printStackTrace();
Log.e("fuck", e.getMessage());
return result;
}
}
}
LeftAdapter.class
package com.cc.test.adapter;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;
import com.cc.test.R;
import com.cc.test.listener.RvItemClickListener;
import com.cc.test.base.BaseAdapter;
import com.cc.test.base.BaseHolder;
import java.util.List;
/**
* 左边列表的适配器
* 14点57分
*
* @author cc
*/
public class LeftAdapter extends BaseAdapter<String> {
//当前选中的item下标
private int checkedPosition;
/**
* 获取当前选中的item下标
*
* @param checkedPosition
*/
public void setCheckedPosition(int checkedPosition) {
this.checkedPosition = checkedPosition;
notifyDataSetChanged();
}
/**
* 构造方法
*
* @param context 上下文
* @param list item数据集合
* @param listener 点击事件
*/
public LeftAdapter(Context context, List<String> list, RvItemClickListener listener) {
super(context, list, listener);
}
/**
* 获取布局文件
*
* @param viewType
* @return
*/
@Override
protected int getLayoutId(int viewType) {
return R.layout.item_left_detail;
}
/**
* holder
*
* @param view
* @param viewType
* @return
*/
@Override
protected BaseHolder getHolder(View view, int viewType) {
return new SortHolder(view, listener);
}
/**
* 处理数据交互
*/
private class SortHolder extends BaseHolder<String> {
//左边的item内容
private TextView tvName;
SortHolder(View itemView, RvItemClickListener listener) {
super(itemView, listener);
tvName = itemView.findViewById(R.id.tv_sort);
}
@Override
public void bindHolder(String string, int position) {
//填充左边的item内容
tvName.setText(string);
//处理选中与未选中的ui状态
tvName.setBackgroundColor(
position == checkedPosition
? Color.parseColor("#f3f3f3")
: Color.parseColor("#FFFFFF")
);
tvName.setTextColor(
position == checkedPosition
? Color.parseColor("#0068cf")
: Color.parseColor("#1e1d1d")
);
}
}
}
RightAdapter.class
package com.cc.test.adapter;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.cc.test.R;
import com.cc.test.listener.RvItemClickListener;
import com.cc.test.base.BaseAdapter;
import com.cc.test.base.BaseHolder;
import com.cc.test.model.RightBean;
import java.util.List;
/**
* 右边的适配器
* 14点58分
*
* @author cc
*/
public class RightAdapter extends BaseAdapter<RightBean> {
/**
* 构造方法
*
* @param context
* @param list
* @param listener
*/
public RightAdapter(Context context, List<RightBean> list, RvItemClickListener listener) {
super(context, list, listener);
}
/**
* 布局view
*
* @param viewType
* @return
*/
@Override
protected int getLayoutId(int viewType) {
return viewType == 0 ? R.layout.item_right_title : R.layout.item_right_detail;
}
/**
* 根据isTitle确认是否未头部吸附布局
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return list.get(position).isTitle() ? 0 : 1;
}
@Override
protected BaseHolder getHolder(View view, int viewType) {
return new ClassifyHolder(view, viewType, listener);
}
public class ClassifyHolder extends BaseHolder<RightBean> {
TextView tvCity;
ImageView avatar;
TextView tvTitle;
public ClassifyHolder(View itemView, int type, RvItemClickListener listener) {
super(itemView, listener);
switch (type) {
case 0:
tvTitle = itemView.findViewById(R.id.tv_title);
break;
case 1:
tvCity = itemView.findViewById(R.id.tvCity);
avatar = itemView.findViewById(R.id.ivAvatar);
break;
}
}
@Override
public void bindHolder(RightBean sortBean, int position) {
int itemViewType = RightAdapter.this.getItemViewType(position);
switch (itemViewType) {
case 0:
tvTitle.setText(sortBean.getName());
break;
case 1:
tvCity.setText(sortBean.getName());
break;
}
}
}
}
自定义分割线
package com.cc.test.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.cc.test.R;
import com.cc.test.listener.RightItemCheckListener;
import com.cc.test.model.RightBean;
import java.util.List;
/**
* 吸附的分割布局
* 14点57分
*
* @author cc
*/
public class ItemHeaderDecoration extends RecyclerView.ItemDecoration {
private int mTitleHeight;
private List<RightBean> mDatas;
private LayoutInflater mInflater;
private RightItemCheckListener mCheckListener;
public static String currentTag = "0";//标记当前左侧选中的position,因为有可能选中的item,右侧不能置顶,所以强制替换掉当前的tag
public void setCheckListener(RightItemCheckListener checkListener) {
mCheckListener = checkListener;
}
public ItemHeaderDecoration(Context context, List<RightBean> datas) {
this.mDatas = datas;
Paint paint = new Paint();
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
int titleFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
paint.setTextSize(titleFontSize);
paint.setAntiAlias(true);
mInflater = LayoutInflater.from(context);
}
public ItemHeaderDecoration setData(List<RightBean> mDatas) {
this.mDatas = mDatas;
return this;
}
public static void setCurrentTag(String currentTag) {
ItemHeaderDecoration.currentTag = currentTag;
}
@Override
public void onDrawOver(Canvas canvas, final RecyclerView parent, RecyclerView.State state) {
GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = manager.getSpanSizeLookup();
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
int spanSize = spanSizeLookup.getSpanSize(pos);
Log.d("pos--->", String.valueOf(pos));
String tag = mDatas.get(pos).getTag();
View child = parent.findViewHolderForLayoutPosition(pos).itemView;
boolean isTranslate = false;//canvas是否平移的标志
if (!TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 1).getTag())
|| !TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 2).getTag())
|| !TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 3).getTag())
) {
tag = mDatas.get(pos).getTag();
int i = child.getHeight() + child.getTop();
Log.d("i---->", String.valueOf(i));
if (spanSize == 1) {
//body 才平移
if (child.getHeight() + child.getTop() < mTitleHeight) {
canvas.save();
isTranslate = true;
int height = child.getHeight() + child.getTop() - mTitleHeight;
canvas.translate(0, height);
}
}
}
drawHeader(parent, pos, canvas);
if (isTranslate) {
canvas.restore();
}
Log.d("tag--->", tag + "VS" + currentTag);
if (!TextUtils.equals(tag, currentTag)) {
currentTag = tag;
Integer integer = Integer.valueOf(tag);
mCheckListener.check(integer, false);
}
}
/**
* @param parent
* @param pos
*/
private void drawHeader(RecyclerView parent, int pos, Canvas canvas) {
View topTitleView = mInflater.inflate(R.layout.item_right_title, parent, false);
TextView tvTitle = (TextView) topTitleView.findViewById(R.id.tv_title);
tvTitle.setText(mDatas.get(pos).getTitleName());
//绘制title开始
int toDrawWidthSpec;//用于测量的widthMeasureSpec
int toDrawHeightSpec;//用于测量的heightMeasureSpec
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) topTitleView.getLayoutParams();
if (lp == null) {
lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//这里是根据复杂布局layout的width height,new一个Lp
topTitleView.setLayoutParams(lp);
}
topTitleView.setLayoutParams(lp);
if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
//如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY);
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//如果是WRAP_CONTENT,则用父控件能分配的最大宽度和AT_MOST构建MeasureSpec
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
} else {
//否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
}
//高度同理
if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
} else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
} else {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(mTitleHeight, View.MeasureSpec.EXACTLY);
}
//依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上
topTitleView.measure(toDrawWidthSpec, toDrawHeightSpec);
topTitleView.layout(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getPaddingLeft() + topTitleView.getMeasuredWidth(), parent.getPaddingTop() + topTitleView.getMeasuredHeight());
topTitleView.draw(canvas);//Canvas默认在视图顶部,无需平移,直接绘制
//绘制title结束
}
}
还有部分代码就不一一贴出来了。
呐,代码就这些啦,简单吧~~
附上demo源码。
源码:源码请点这里
如果下不了源码,请评论带邮箱。
phone:18588400509
email:mr.cai_cai@foxmail.com
如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。
---财财亲笔