项目要用到粘性头部,以前的ListView和GridView的还好整,RecycleView的一片茫然,在github上找了很多发现好复杂,使用ItemDecoration实现,这货以我的智商真难搞懂,或者只适配了LinearLayoutManager和GridLayoutManager,很少适配了StaggeredGridLayoutManager,我的需求恰恰是瀑布流,只设置两个粘性头部,于是我利用帧布局and监听滑动事件移动布局来实现了这一需求。
首先看下Demo的效果图吧:
是不是还过得去呢^_^,接下来贴代码,代码有详细的注释了
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:scrollbars="none" />
<RelativeLayout android:id="@+id/rl_sticky_head" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="2.5dp" android:layout_marginRight="2.5dp">
<include layout="@layout/flashgo_header" />
</RelativeLayout>
<RelativeLayout android:id="@+id/rl_sticky_head_fake" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="2.5dp" android:layout_marginRight="2.5dp" android:visibility="invisible">
<include android:id="@+id/tv_fake_sticky_head" layout="@layout/flashgo_header" />
</RelativeLayout>
</FrameLayout>
主布局为一个帧布局,包含了RecyclerView和两个flashgo_header,flashgo_header布局是用来做为粘性头部的,RecyclerView在位置0那里会相应地添加一个flashgo_header布局,这样子RecyclerView那么真正要展示的图片位置就不会收到遮掩了,
那么干哈要两个呢,rl_sticky_head是作为Header One使用的;rl_sticky_head_fake是作为Header Two使用的,最外层就是它了,默认为INVISIBLE。
它的使用如下图讲解所示,是用来伪造RecycleView头部二到顶部固定的效果
然后就是MainActivity的代码了:
该解释的都解释了……
public class MainActivity extends AppCompatActivity{
private int mHalfScreenWidth;
private RecyclerView mRecyclerView;
private RecyclerAdapter mAdapter;
private StaggeredGridLayoutManager mManager;
private RelativeLayout mStickyHeadLayout;
private RelativeLayout mFakeStickyHeadLayout;
private TextView mStickyHead;
private TextView mFakeStickyHead;
private int mStickyHeadHeight;
private int mHeaderOneCount = 0;
private List<Integer> mProductInfos;
private int mProductInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle("瀑布流粘性头部Demo");
}
// 帧布局Header One的头部
mStickyHeadLayout = (RelativeLayout) findViewById(R.id.rl_sticky_head);
mStickyHead = (TextView) findViewById(R.id.tv_sticky_head);
mStickyHead.setText("Header One");
mStickyHead.measure(0, 0);
// 获取头部的高度,作为是否移动头部的范围
mStickyHeadHeight = mStickyHead.getMeasuredHeight();
// 帧布局的Header Two的头部,在帧布局里面的最外的一层,
// 默认为不可见,用于recycleview移动真正的第二个头部到刚刚出界面时设置为可见
// 形成一种recycleview真正的第二个头部到顶端停住的效果
// 实际recycleview真正的第二个头部还是继续上移,只是帧布局的Header Two的头部设置为可见而已
mFakeStickyHeadLayout = (RelativeLayout) findViewById(R.id.rl_sticky_head_fake);
mFakeStickyHead = (TextView) findViewById(R.id.tv_fake_sticky_head);
mFakeStickyHead.setText("Header Two");
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.addOnScrollListener(new MyScrollListener());
}
class MyScrollListener extends RecyclerView.OnScrollListener {
// 用于获取瀑布流的不完全可见的位置,0为左边不完全可见的位置,1为右边不完全可见的位置
int mVisiblePosition[] = new int[2];
// recycleview的第二个头部,内容高度跟帧布局的Header Two的头部一模一样
private View header;
// 用于recycleview的第二个头部刚刚进入帧布局Header One的头部的范围时,
// 强制设置位移量dy为1,防止用户上推过快,dy过大造成的帧布局Header One的头部不同步移动
boolean isFirstIn;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 查找第一个不完全可见的左右两个item的位置
mManager.findFirstVisibleItemPositions(mVisiblePosition);
// 获取第二个头部的示例,有可能为null
header = mManager.findViewByPosition(mHeaderOneCount + 1);
if (mHeaderOneCount != 0
&& header != null
&& ViewHelper.getY(header) > 0
&& ViewHelper.getY(header) < mStickyHeadHeight) {
// 0<ViewHelper.getY(header)<mStickyHeadHeight 说明recycleview的第二个头部
// 进入帧布局Header One的头部高度的范围,此时可以移动帧布局Header One的头部
if (!isFirstIn) {
// 刚刚进入高度范围强制设置位移量dy为1,防止用户上推过快,
// dy过大造成的帧布局Header One的头部不同步移动
isFirstIn = true;
dy = 1;
}
// 移动帧布局Header One的头部
mStickyHeadLayout.scrollBy(0, dy);
// 在此过程中帧布局的Header Two的头部设为不可见
mFakeStickyHeadLayout.setVisibility(View.INVISIBLE);
if (mStickyHeadLayout.getScrollY() < 0) {
// 防止上推过快,移动帧布局Header One的头部上移超过其高度
mStickyHeadLayout.scrollBy(0, -mStickyHeadLayout.getScrollY());
}
} else if (mHeaderOneCount != 0
&& header != null
&& ViewHelper.getY(header) <= 0) {
// recycleview的第二个头部刚刚移出界面的时候,设置帧布局的Header Two的头部为可见
// 这样看上去好像recycleview的第二个头部刚好停在顶部
mFakeStickyHeadLayout.setVisibility(View.VISIBLE);
} else if (mHeaderOneCount != 0
&& header != null
&& ViewHelper.getY(header) >= mStickyHeadHeight) {
// recycleview的第二个头部超出帧布局Header One的头部高度的范围,
// 此时设置isFirstIn为false方便下次上推设dy值
isFirstIn = false;
}
if (header != null && dy > 0 && mStickyHeadLayout.getScrollY() != mStickyHeadHeight && ViewHelper.getY(header) < 0) {
// 这个情况是针对recycleview的第二个头部移出布局的时候,由于上推滑动过快,
// 造成的同步上移的帧布局Header One的头部未完全移出或移出超过其高度
// 这里再调整帧布局Header One的头部上移距离为其高度,即刚刚好移出界面
mStickyHeadLayout.scrollBy(0, mStickyHeadHeight - mStickyHeadLayout.getScrollY());
} else if (header != null
// 这个情况是针对下推的时候,recycleview的第二个头部超出帧布局Header One的头部高度的范围,
// 但是帧布局Header One的头部未完全下移到原来的位置,这里再调整它移回到最初的位置
&& dy < 0
&& mStickyHeadLayout.getScrollY() > 0
&& ViewHelper.getY(header) > mStickyHeadHeight
// 这个起来是针对使用RecyclerView.scrollToPosition(0)的时候,如果此时已经上推了帧布局Header One的头部,
// 此时帧布局Header One的头部不会回到原来位置,因为scrollToPosition太快了
// 上面的ViewHelper.getY(header) > 0 && ViewHelper.getY(header) < mStickyHeadHeight此处的判断执行不了
// 下移不回原来的位置,所以这里特殊处理,如果位置为0的时候,帧布局Header One的头部的scrollY不为0,强制移回原处
|| (mStickyHeadLayout.getScrollY() > 0 && mVisiblePosition[0] == 0)) {
mStickyHeadLayout.scrollBy(0, -mStickyHeadLayout.getScrollY());
}
if (mVisiblePosition[0] > mHeaderOneCount + 1 && mFakeStickyHeadLayout.getVisibility() == View.INVISIBLE) {
// 针对用户超快速滑动情况,当位置大于第二个头部的时候,如果FakeStickyHeadLayout还为不可见的话,需要设为可见
mFakeStickyHeadLayout.setVisibility(View.VISIBLE);
} else if (mVisiblePosition[0] < mHeaderOneCount + 1 && mFakeStickyHeadLayout.getVisibility() == View.VISIBLE) {
// 针对用户超快速滑动情况,当位置小于第二个头部的时候,如果FakeStickyHeadLayout还为可见的话,需要设为不可见
mFakeStickyHeadLayout.setVisibility(View.INVISIBLE);
}
}
}
class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private StaggeredGridLayoutManager.LayoutParams lp;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
if (position == 0) {
outRect.top = 0;
} else if (position == mHeaderOneCount + 1) {
outRect.top = 0;
} else {
lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
// 左item的对右间隔设为0,保证item间隔一致
if (lp.getSpanIndex() == 0) {
outRect.right = 0;
}
}
}
}
private void initData() {
mProductInfos = new ArrayList<>();
mProductInfos.add(R.drawable.ic_girls_0);
mProductInfos.add(R.drawable.ic_girls_1);
mProductInfos.add(R.drawable.ic_girls_2);
mProductInfos.add(R.drawable.ic_girls_3);
mProductInfos.add(R.drawable.ic_girls_4);
mProductInfos.add(R.drawable.ic_girls_5);
mProductInfos.add(R.drawable.ic_girls_6);
mProductInfos.add(R.drawable.ic_girls_7);
mProductInfos.add(R.drawable.ic_girls_8);
mProductInfos.add(R.drawable.ic_girls_9);
mProductInfos.add(R.drawable.ic_girls_10);
// Header One下面的图片总数
mHeaderOneCount = mProductInfos.size();
mProductInfos.add(R.drawable.ic_view_0);
mProductInfos.add(R.drawable.ic_view_1);
mProductInfos.add(R.drawable.ic_view_2);
mProductInfos.add(R.drawable.ic_view_3);
mProductInfos.add(R.drawable.ic_view_4);
mProductInfos.add(R.drawable.ic_view_5);
mProductInfos.add(R.drawable.ic_view_6);
mProductInfos.add(R.drawable.ic_view_7);
mProductInfos.add(R.drawable.ic_view_8);
mProductInfos.add(R.drawable.ic_view_9);
mProductInfos.add(R.drawable.ic_view_10);
mProductInfos.add(R.drawable.ic_view_11);
mProductInfos.add(R.drawable.ic_view_12);
mProductInfos.add(R.drawable.ic_view_13);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mHalfScreenWidth = metrics.widthPixels / 2;
// 瀑布流,两列,垂直方向
mManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mManager);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(getResources().getDimensionPixelSize(R.dimen.common_margin_left)));
mAdapter = new RecyclerAdapter();
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
// recycleview返回位置0
mRecyclerView.scrollToPosition(0);
return true;
}
return super.onOptionsItemSelected(item);
}
class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder> {
class RecyclerViewHolder extends RecyclerView.ViewHolder {
// item展示图片
ImageView mProductImage;
// 头部的文字
TextView stickyTextview;
public RecyclerViewHolder(View itemView) {
super(itemView);
mProductImage = (ImageView) itemView.findViewById(R.id.iv_home_product);
stickyTextview = (TextView) itemView.findViewById(R.id.tv_sticky_head);
}
}
// 头部类型
public final int VIEW_TYPE_HEADER = 0;
// item类型
public final int VIEW_TYPE_REPLY = 1;
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerViewHolder viewHolder;
switch (viewType) {
case VIEW_TYPE_HEADER:
viewHolder = new RecyclerViewHolder
(LayoutInflater.from(MainActivity.this).inflate(R.layout.flashgo_header, parent, false));
return viewHolder;
case VIEW_TYPE_REPLY:
viewHolder = new RecyclerViewHolder
(LayoutInflater.from(MainActivity.this).inflate(R.layout.recycleview_item, parent, false));
return viewHolder;
}
return null;
}
// 封装图片宽高
ImageSize mImageSize;
@Override
public void onBindViewHolder(final RecyclerViewHolder holder, final int position) {
switch (getItemViewType(position)) {
case VIEW_TYPE_HEADER:
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) holder.stickyTextview.getLayoutParams();
// 占满一行
lp.setFullSpan(true);
if (position == 0) {
holder.stickyTextview.setText("Header One");
} else {
holder.stickyTextview.setText("Header Two");
}
holder.stickyTextview.setLayoutParams(lp);
break;
case VIEW_TYPE_REPLY:
if (position <= mHeaderOneCount) {
// 头部一占一个位置
mProductInfo = mProductInfos.get(position - 1);
} else {
// 头部一和二占两个位置
mProductInfo = mProductInfos.get(position - 2);
}
// 获取图片size
mImageSize = getImageSize(MainActivity.this, mProductInfo);
// 图片实际宽度设为屏幕一半,再等比例等到高度
final ViewGroup.LayoutParams lp1 = holder.mProductImage.getLayoutParams();
lp1.width = mHalfScreenWidth;
lp1.height = lp1.width * mImageSize.imageHeight / mImageSize.imageWidth;
holder.mProductImage.setLayoutParams(lp1);
// 加载图片
Glide.with(MainActivity.this)
.load(mProductInfo)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.ic_loading)
.override(lp1.width, lp1.height)
.into(holder.mProductImage);
break;
}
}
@Override
public int getItemViewType(int position) {
// 根据位置确定itemview的类型
return position == 0 || position == mHeaderOneCount + 1 ? VIEW_TYPE_HEADER : VIEW_TYPE_REPLY;
}
@Override
public int getItemCount() {
// 处理mProductInfos还有两个头部
return mProductInfos.size() + 2;
}
}
private ImageSize getImageSize(Context context, int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), resId, options);
return new ImageSize(options.outWidth, options.outHeight);
}
class ImageSize {
int imageHeight;
int imageWidth;
public ImageSize(int imageWidth, int imageHeight) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
}
}
}
总结:此种实现方式适用于少量头部的时候,主要在RecyclerView.OnScrollListener的onScrolled方法获取偏移量dy,并判断RecycleView头部二的y坐标是否进入到帧布局Header One的范围里面,然后调用scrollBy(0,dy)移动帧布局Header One,并根据RecycleView头部二离开界面时设置帧布局Header Two可见伪造固定在顶部效果,难点在于滑动速度过快导致的dy过大造成的布局位移过小或过大,经本人特殊处理过已无大问题,能有良好的效果。希望对看到的朋友有所启发,本人觉得这种方法还是稍逊一筹,但是不失为一种解题思路吧。
最后附上源代码地址:
http://download.csdn.net/detail/oushangfeng123/8998383