本demo基于二级分类双列表联动Demo进行了改进,高仿实现了京东的三级类型列表。
京东的如图:
本demo的:
改进之处
实现了三级列表联动,二三级列表之间的滑动监听优化了一下,将二级类型选中交予自身的点击事件,不再完全依靠三级列表的滑动回调。
上代码
引入的依赖
//ShapeView
implementation 'com.github.getActivity:ShapeView:9.0'
//Gson解析
implementation 'com.google.code.gson:gson:2.10.1'
ShapeView主要是美化一下二级条目,Gson是为了解析本地的类型json数据
条目布局
item_type_one
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_main_left_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginVertical="15dp" />
</LinearLayout>
item_type_two
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@color/flow">
<com.hjq.shape.view.ShapeButton
android:id="@+id/type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:gravity="center"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
android:textSize="18sp"
app:shape_radius="10dp"
app:shape_solidColor="@android:color/transparent"
app:shape_strokeColor="@color/common_accent_color"
app:shape_strokeSize="1dp"
app:shape_textColor="@color/common_accent_color"
app:shape_textPressedColor="@android:color/white"
app:shape_type="rectangle" />
</LinearLayout>
item_type_three
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/radius_white"
android:layout_margin="10dp"
android:padding="10dp">
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:paddingHorizontal="10dp"
app:drawableEndCompat="@drawable/arrows_right_ic" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
item_type_four(其实这个才是类型三,上面的是类型三的外围包裹,但是不想改了)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:src="@mipmap/ic_logo"
android:layout_margin="5dp"
android:layout_width="60dp"
android:layout_height="60dp" />
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="14sp"
android:text="男鞋" />
</LinearLayout>
主页面布局
由两个列表变成了三个列表
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/flow"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/one"
android:layout_weight="7"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_weight="3"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/two"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/three"
android:layout_weight="1"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="0dp"/>
</LinearLayout>
</LinearLayout>
适配器
OneTypeAdapter
public class OneTypeAdapter extends RecyclerView.Adapter<OneTypeAdapter.OneTypeHolder> {
private final Context context;
private final List<GoodsTypeBN> list;
public int selectedPosition = 0;//当前选择的下标
private OnItemClickListener onItemClickListener;
public OneTypeAdapter(Context context, List<GoodsTypeBN> list) {
this.context = context;
this.list = list;
}
@NonNull
@Override
public OneTypeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.item_type_one,parent,false);
return new OneTypeHolder(view);
}
@Override
public void onBindViewHolder(@NonNull OneTypeHolder holder, @SuppressLint("RecyclerView") int position) {
holder.type.setText(list.get(position).getType_name());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (selectedPosition != holder.getAdapterPosition()) {
//点击了新的 item,更新状态
selectedPosition = holder.getAdapterPosition();
}
if (onItemClickListener!=null){
onItemClickListener.onItemClickListener(v,position);
}
notifyDataSetChanged();
}
});
if (position==selectedPosition){
holder.type.setTextColor(context.getColor(R.color.red));
holder.type.setTextSize(30);
}
else {
holder.type.setTextColor(context.getColor(R.color.black));
holder.type.setTextSize(18);
}
}
@Override
public int getItemCount() {
return list.size();
}
public static class OneTypeHolder extends RecyclerView.ViewHolder{
TextView type;
public OneTypeHolder(@NonNull View itemView) {
super(itemView);
type = itemView.findViewById(R.id.type);
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClickListener(View v, int position);
}
}
TwoTypeAdapter
public class TwoTypeAdapter extends RecyclerView.Adapter<TwoTypeAdapter.TwoTypeHolder> {
private Context context;
private List<GoodsTypeBN> list;
private OnItemClickListener onItemClickListener;
public int selectPosition=0;
public TwoTypeAdapter(Context context, List<GoodsTypeBN> list) {
this.context = context;
this.list = list;
}
@NonNull
@Override
public TwoTypeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.item_type_two,parent,false);
return new TwoTypeHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TwoTypeHolder holder, int position) {
holder.type.setText(list.get(position).getType_name());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
selectPosition=position;
notifyDataSetChanged();
if (onItemClickListener!=null){
onItemClickListener.onItemClickListener(view,position);
}
}
});
if (position==selectPosition){
holder.type.setTextColor(context.getColor(R.color.common_accent_color));
holder.type.getShapeDrawableBuilder()
.setStrokeColor(context.getColor(R.color.common_accent_color))
.intoBackground();
}
else {
holder.type.setTextColor(context.getColor(R.color.grey));
holder.type.getShapeDrawableBuilder()
.setStrokeColor(context.getColor(R.color.grey))
.intoBackground();
}
}
@Override
public int getItemCount() {
return list.size();
}
public static class TwoTypeHolder extends RecyclerView.ViewHolder{
ShapeButton type;
public TwoTypeHolder(@NonNull View itemView) {
super(itemView);
type = itemView.findViewById(R.id.type);
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClickListener(View v, int position);
}
}
注意这里,这里二级条目的点击事件下,会将自身选中,自己来选中自己,然后更新,不再交由列表的滑动来更新。
之前的是这样。因为后面的几个条目点击存在一些问题。
ThreeTypeAdapter
public class ThreeTypeAdapter extends RecyclerView.Adapter<ThreeTypeAdapter.ThreeTYpeHolder> {
private final Context context;
private final List<GoodsTypeBN> list;
public ThreeTypeAdapter(Context context, List<GoodsTypeBN> list) {
this.context = context;
this.list = list;
}
@NonNull
@Override
public ThreeTYpeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.item_type_three,parent,false);
return new ThreeTYpeHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ThreeTYpeHolder holder, int position) {
holder.type.setText(list.get(position).getType_name());
holder.recyclerView.setAdapter(new FourAdapter(context,list.get(position).getNextType()));
holder.recyclerView.setLayoutManager(new GridLayoutManager(context,3));
}
@Override
public int getItemCount() {
return list.size();
}
public static class ThreeTYpeHolder extends RecyclerView.ViewHolder{
TextView type;
RecyclerView recyclerView;
public ThreeTYpeHolder(@NonNull View itemView) {
super(itemView);
type = itemView.findViewById(R.id.type);
recyclerView = itemView.findViewById(R.id.recycler);
}
}
}
FourAdapter
public class FourAdapter extends RecyclerView.Adapter<FourAdapter.FourHolder> {
private final Context context;
private final List<GoodsTypeBN> list;
public FourAdapter( Context context,List<GoodsTypeBN> list) {
this.context = context;
this.list = list;
}
@NonNull
@Override
public FourHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.item_type_four,parent,false);
return new FourHolder(view);
}
@Override
public void onBindViewHolder(@NonNull FourHolder holder, int position) {
GoodsTypeBN goodsTypeBN=list.get(position);
holder.type.setText(goodsTypeBN.getType_name());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(context, "点击了"+goodsTypeBN.getType_name(), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return list.size();
}
public static class FourHolder extends RecyclerView.ViewHolder{
TextView type;
ImageView icon;
public FourHolder(@NonNull View itemView) {
super(itemView);
type = itemView.findViewById(R.id.type);
icon = itemView.findViewById(R.id.icon);
}
}
}
主要代码
GoodsTypeBN类
public class GoodsTypeBN {
private String type_id;
private String type_name;
private String parent_id;
private String type_lv;
private List<GoodsTypeBN> nextType;
public String getType_id() {
return type_id;
}
public void setType_id(String type_id) {
this.type_id = type_id;
}
public String getType_name() {
return type_name;
}
public void setType_name(String type_name) {
this.type_name = type_name;
}
public String getParent_id() {
return parent_id;
}
public void setParent_id(String parent_id) {
this.parent_id = parent_id;
}
public String getType_lv() {
return type_lv;
}
public void setType_lv(String type_lv) {
this.type_lv = type_lv;
}
public List<GoodsTypeBN> getNextType() {
return nextType;
}
public void setNextType(List<GoodsTypeBN> nextType) {
this.nextType = nextType;
}
@Override
public String toString() {
return "GoodsTypeBN{" +
"type_id='" + type_id + '\'' +
", type_name='" + type_name + '\'' +
", parent_id='" + parent_id + '\'' +
", type_lv='" + type_lv + '\'' +
", nextType=" + nextType +
'}';
}
}
主页面逻辑
//一级类型条目点击
oneTypeAdapter.setOnItemClickListener(new OneTypeAdapter.OnItemClickListener() {
@Override
public void onItemClickListener(View v, int position) {
//更换二级三级类型的数据
typeTwo.clear();
typeTwo.addAll(typeOne.get(position).getNextType());
twoTypeAdapter.selectPosition=0;
twoTypeAdapter.notifyDataSetChanged();
threeTypeAdapter.notifyDataSetChanged();
}
});
//二级类型条目点击
twoTypeAdapter.setOnItemClickListener(new TwoTypeAdapter.OnItemClickListener() {
@Override
public void onItemClickListener(View v, int position) {
//将三级类型滑动到相应位置
LinearLayoutManager threeLayoutManager = ((LinearLayoutManager) three.getLayoutManager());
if (threeLayoutManager!=null){
threeLayoutManager.scrollToPositionWithOffset(position,0);
}
}
});
//添加三级类型滑动监听
three.addOnScrollListener(onScrollListener);
添加滑动监听
RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
/**
获取第一个可见的item的position
*/
int currentPosition = 0;
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager twoLayoutManager = (LinearLayoutManager) two.getLayoutManager();
LinearLayoutManager threeLayoutManager = ((LinearLayoutManager) three.getLayoutManager());
if (twoLayoutManager!=null&&threeLayoutManager!=null){
currentPosition = threeLayoutManager.findFirstCompletelyVisibleItemPosition();
/**
这地方需要进行判断,如果下面的Recycle在移动的时候,上面的RecyclerView也是需要进行移动的
上面的recyclerview有可能会不可见,这时候,我们必须去判断一下,上面最后的一个item是不是
小于下面滑动的位置,或上面第一个item是不是大于下面滑动的位置
*/
if (twoLayoutManager.findFirstCompletelyVisibleItemPosition() > currentPosition) {
twoLayoutManager.scrollToPositionWithOffset(currentPosition, 0);
} else if (twoLayoutManager.findFirstCompletelyVisibleItemPosition() < currentPosition) {
twoLayoutManager.scrollToPositionWithOffset(currentPosition, 0);
}
// 判断滚动的方向和位置,判断是否触发了回弹效果
if (dy < 0 && !recyclerView.canScrollVertically(-1)) {
// 触发了上拉的回弹效果
Log.e("TAG", "onScrolled: 触发了上拉的回弹效果" );
} else if (dy > 0 && !recyclerView.canScrollVertically(1)) {
// 触发了下拉的回弹效果
currentPosition = typeTwo.size() - 1;
Log.e("TAG", "onScrolled: 触发了下拉的回弹效果" );
}
twoTypeAdapter.selectPosition=currentPosition;
twoTypeAdapter.notifyDataSetChanged();
}
}
};
全部代码
public class MainActivity extends AppCompatActivity {
private OneTypeAdapter oneTypeAdapter;
private TwoTypeAdapter twoTypeAdapter;
private ThreeTypeAdapter threeTypeAdapter;
private List<GoodsTypeBN> typeOne;
private List<GoodsTypeBN> typeTwo=new ArrayList<>();
private List<GoodsTypeBN> typeThree=new ArrayList<>();
private RecyclerView one,two,three;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
one=findViewById(R.id.one);
two=findViewById(R.id.two);
three=findViewById(R.id.three);
init();
}
private void init(){
String json = getJson(this, "category.json");
if (!TextUtils.isEmpty(json)){
//解析数据
List<GoodsTypeBN> typeBNBaseEN=new Gson().fromJson(json, new TypeToken<List<GoodsTypeBN>>(){}.getType());
if (typeBNBaseEN!=null){
typeOne=typeBNBaseEN;
//设置一级类型
oneTypeAdapter=new OneTypeAdapter(this,typeOne);
one.setLayoutManager(new LinearLayoutManager(this));
one.setAdapter(oneTypeAdapter);
//设置初始二级类型
typeTwo.clear();
typeTwo.addAll(typeOne.get(0).getNextType());
twoTypeAdapter=new TwoTypeAdapter(this,typeTwo);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
two.setLayoutManager(linearLayoutManager);
two.setAdapter(twoTypeAdapter);
//设置初始三级类型
typeThree.clear();
typeThree.addAll(typeTwo.get(0).getNextType());
threeTypeAdapter=new ThreeTypeAdapter(this,typeTwo);
three.setLayoutManager(new LinearLayoutManager(this));
three.setAdapter(threeTypeAdapter);
//一级类型条目点击
oneTypeAdapter.setOnItemClickListener(new OneTypeAdapter.OnItemClickListener() {
@Override
public void onItemClickListener(View v, int position) {
//更换二级三级类型的数据
typeTwo.clear();
typeTwo.addAll(typeOne.get(position).getNextType());
twoTypeAdapter.selectPosition=0;
twoTypeAdapter.notifyDataSetChanged();
threeTypeAdapter.notifyDataSetChanged();
}
});
//二级类型条目点击
twoTypeAdapter.setOnItemClickListener(new TwoTypeAdapter.OnItemClickListener() {
@Override
public void onItemClickListener(View v, int position) {
//将三级类型滑动到相应位置
LinearLayoutManager threeLayoutManager = ((LinearLayoutManager) three.getLayoutManager());
if (threeLayoutManager!=null){
threeLayoutManager.scrollToPositionWithOffset(position,0);
}
}
});
//添加三级类型滑动监听
three.addOnScrollListener(onScrollListener);
}
}
}
RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
/**
获取第一个可见的item的position
*/
int currentPosition = 0;
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager twoLayoutManager = (LinearLayoutManager) two.getLayoutManager();
LinearLayoutManager threeLayoutManager = ((LinearLayoutManager) three.getLayoutManager());
if (twoLayoutManager!=null&&threeLayoutManager!=null){
currentPosition = threeLayoutManager.findFirstCompletelyVisibleItemPosition();
/**
这地方需要进行判断,如果下面的Recycle在移动的时候,上面的RecyclerView也是需要进行移动的
上面的recyclerview有可能会不可见,这时候,我们必须去判断一下,上面最后的一个item是不是
小于下面滑动的位置,或上面第一个item是不是大于下面滑动的位置
*/
if (twoLayoutManager.findFirstCompletelyVisibleItemPosition() > currentPosition) {
twoLayoutManager.scrollToPositionWithOffset(currentPosition, 0);
} else if (twoLayoutManager.findFirstCompletelyVisibleItemPosition() < currentPosition) {
twoLayoutManager.scrollToPositionWithOffset(currentPosition, 0);
}
// 判断滚动的方向和位置,判断是否触发了回弹效果
if (dy < 0 && !recyclerView.canScrollVertically(-1)) {
// 触发了上拉的回弹效果
Log.e("TAG", "onScrolled: 触发了上拉的回弹效果" );
} else if (dy > 0 && !recyclerView.canScrollVertically(1)) {
// 触发了下拉的回弹效果
currentPosition = typeTwo.size() - 1;
Log.e("TAG", "onScrolled: 触发了下拉的回弹效果" );
}
twoTypeAdapter.selectPosition=currentPosition;
twoTypeAdapter.notifyDataSetChanged();
}
}
};
public static String getJson(Context context, String fileName) {
StringBuilder stringBuilder = new StringBuilder();
//获得assets资源管理器
AssetManager assetManager = context.getAssets();
//使用IO流读取json文件内容
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
assetManager.open(fileName), StandardCharsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return stringBuilder.toString();
}
}
源码
github:https://github.com/panzhusheng/JDCategorydemo
gitee:https://gitee.com/pan-zs/JDCategorydemo