本文实现的效果
实现步骤:
1、布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ContentFragment"
android:background="#fff">
<android.support.v7.widget.RecyclerView
android:id="@+id/detail_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
2、列表实现的代码
public class ContentFragment extends Fragment {
private RecyclerView detail_recycler;
public ContentFragment() {
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_content,container,false);
initView(view);
return view;
}
private void initView(View view) {
getData();
detail_recycler = view.findViewById(R.id.detail_recycler);
GridLayoutManager gridLayoutManager=new GridLayoutManager(getContext(),3);
gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
detail_recycler.setLayoutManager(gridLayoutManager);
detail_recycler.addItemDecoration(new MyItem2());
detail_recycler.addItemDecoration(new ItemHeaderDecoration(getActivity(),list));
detail_recycler.setAdapter(new MyAdapter());
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int i) {
return list.get(i).isTitle() ? 3:1;
}
});
}
List<DetailBean> list = new ArrayList<>();
private void getData(){
for (int i=0;i<10;i++){
list.add(new DetailBean(true,"标题"+i,"tag"+i));
for(int j = 0 ; j < 20 ; j++){
list.add(new DetailBean(false,"内容"+j,"tag"+i));
}
}
Log.v("list=======","list="+new Gson().toJson(list));
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private static final int TITLE = 1;
private static final int NORMAL = 2;
@NonNull
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return i==TITLE ? new MyAdapter.MyViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.detail_title, viewGroup, false))
:new MyAdapter.MyViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.item_layout, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull MyAdapter.MyViewHolder holder, int i) {
holder.title_textview.setText(list.get(i).getName());
}
@Override
public int getItemCount() {
return list.size();
}
@Override
public int getItemViewType(int position) {
return list.get(position).isTitle() ? TITLE:NORMAL;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView title_textview;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
title_textview = itemView.findViewById(R.id.title_textview);
}
}
}
}
3、最重要的一步重写 RecyclerView.ItemDecoration 中的 onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)方法
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
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 java.util.List;
/**
* Created by User on 2017/7/24.
*/
public class ItemHeaderDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private List<DetailBean> mList;
public static String currentTag = "0";
private LayoutInflater mLayoutInflater;
private int mTitleHight = 50;
public ItemHeaderDecoration(Context context , List<DetailBean> dataList) {
super();
this.mContext = context;
this.mList = dataList;
mTitleHight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50,context.getResources().getDisplayMetrics());
// mTitleTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
this.mLayoutInflater = LayoutInflater.from(mContext);
}
public static void setCurrentTag(String tag){
ItemHeaderDecoration.currentTag = tag;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
//获取到视图中第一个可见的item的position
GridLayoutManager manager = (GridLayoutManager) parent.getLayoutManager();
int position = manager.findFirstVisibleItemPosition();
String tag = mList.get(position).getTag();
View child = parent.findViewHolderForLayoutPosition(position).itemView;
int nextPosition = position+2;
boolean flag = false ;
if(nextPosition < mList.size()){
String suspensionTag = mList.get(nextPosition).getTag();
Log.v("====child=======","=="+child.getTop());
if(!TextUtils.isEmpty(tag) && !tag.equals(suspensionTag)){
if(child.getHeight()+child.getTop() < mTitleHight){
c.save();
flag = true ;
c.translate(0,child.getHeight()+child.getTop()-mTitleHight);
}
}
}
View topTitleView = mLayoutInflater.inflate(R.layout.detail_title,parent,false);
TextView textView = topTitleView.findViewById(R.id.title_textview);
textView.setText("分类"+tag);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) topTitleView.getLayoutParams();
if (lp != null) {
//依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
topTitleView.measure(mWidth(lp,parent), mHeight(lp,parent));
//topTitleView在屏幕中显示的位置
topTitleViewLayout(parent,topTitleView);
//Canvas默认在视图顶部,无需平移,直接绘制
topTitleView.draw(c);
if (flag){
c.restore();//还原画布
}
}
}
/**
* 显示的位置
*
* @param parent
* @param topTitleView
*/
private void topTitleViewLayout(RecyclerView parent, View topTitleView) {
int l = parent.getPaddingLeft();//如果父布局有内句左边
int t = parent.getTop();//当前距离父布局顶部的距离
int r = parent.getPaddingLeft() + topTitleView.getMeasuredWidth();;
int b = t +topTitleView.getMeasuredHeight();
topTitleView.layout(l,t,r,b);
}
/**
* 高度
*
* @param lp
* @param parent
* @return
*/
private int mHeight(RecyclerView.LayoutParams lp , RecyclerView parent) {
int h;
//高度同理
if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
h = View.MeasureSpec.makeMeasureSpec(
parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
} else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
h = View.MeasureSpec.makeMeasureSpec(
parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
} else {
h = View.MeasureSpec.makeMeasureSpec(mTitleHight, View.MeasureSpec.EXACTLY);
}
return h;
}
/**
* 宽
*
* @param lp
* @param parent
* @return
*/
private int mWidth(RecyclerView.LayoutParams lp, RecyclerView parent) {
int w;
if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
//如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec。
w = 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。
w = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
} else {
//否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec。
w = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
}
return w;
}
/**
* @param val
* @return
*/
public int dpToPx(int val){
float density = mContext.getResources().getDisplayMetrics().density;
float px = val * density + 0.5f;
return (int) px;
}
}
两个吸附的view交替过程的实现
if(nextPosition < mList.size()){
String suspensionTag = mList.get(nextPosition).getTag();
Log.v("====child=======","=="+child.getTop());
if(!TextUtils.isEmpty(tag) && !tag.equals(suspensionTag)){
if(child.getHeight()+child.getTop() < mTitleHight){
c.save();
flag = true ;
c.translate(0,child.getHeight()+child.getTop()-mTitleHight);
}
}
}
真正实现吸附部分
if (lp != null) {
//依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
topTitleView.measure(mWidth(lp,parent), mHeight(lp,parent));
//topTitleView在屏幕中显示的位置
topTitleViewLayout(parent,topTitleView);
//Canvas默认在视图顶部,无需平移,直接绘制
topTitleView.draw(c);
if (flag){
c.restore();//还原画布
}
}
以上的代码就可以实现吸附效果了。