当前位置 博文首页 > 启舰:自定义控件三部曲视图篇(九)——RecycerView系列之六实
把握生命里的每一分钟,全力以赴我们心中的梦,不经历风雨,怎么见彩虹,没有人能随随便便成功 -----《真心英雄》
系列文章: Android自定义控件三部曲文章索引: http://blog.csdn.net/harvic880925/article/details/50995268
本节是我的新书《Android自定义控件高级进阶与精彩实例》中的一小节,目前还在著作中,预计2020年上市,本来没打算更出来,可有些同学评论非常需要这个效果,就摘取出来分享给大家,前面的章节序号我就不改了……(我太懒)
在上一章中,我们讲了RecyclerView的各种基础知识,在这章中,我们将通过非常炫酷的特效来实际学习下RecyclerView。可以看到,通过这些看似平淡的功能,能做出非常漂亮的控件,现在我们就开始吧。
本节将实现在上一章中提到过的画廊效果,但为了减轻难度,就不再制作3D画廊,而是制作出2D的,不过最后将在2D的基础上,讲解3D画廊的实现原理,本节实现的效果如下图所示:
高能预警:本节代码量较大,而且是利用4.5节代码修改而来,对于同一个函数因为逻辑实现次序的问题,可能会多次修改,而且由于篇幅有限,并不能每次贴出全部源码,只能截取核心部分,所以建议大家对照着源码看本节文章,不然半路有可能会蒙……。
这节内容,我们实现原理与4.5节基本相同,所以很多代码,大家理解起来应该都不难,有些部分就不再细讲。在这部分,我们先在4.5节代码的基础上做修改,以便很快可以看到效果。首先,我们先把4.5节中的item布局更改为我们想要的布局(item_coverflow.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:text="0"
android:layout_gravity="center"
android:textColor="@android:color/black"/>
<ImageView
android:id="@+id/img"
android:layout_marginTop="10dp"
android:layout_width="300dp"
android:layout_height="200dp"
android:scaleType="centerCrop"/>
</LinearLayout>
布局很好理解,就是垂直排列一个text和一个img。text用于显示当前item的位置,img用于显示图片。
所以,我们还需要引用几个图片资源,源码中放在mipmap-xxhdpi文件夹下:
然后,我们新建一个Adapter(CoverFlowAdapter):
public class CoverFlowAdapter extends Adapter<ViewHolder> {
private Context mContext;
private ArrayList<String> mDatas;
private int mCreatedHolder=0;
private int[] mPics = {R.mipmap.item1,R.mipmap.item2,R.mipmap.item3,R.mipmap.item4,
R.mipmap.item5,R.mipmap.item6};
public CoverFlowAdapter(Context context, ArrayList<String> datas) {
mContext = context;
mDatas = datas;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mCreatedHolder++;
LayoutInflater inflater = LayoutInflater.from(mContext);
return new NormalHolder(inflater.inflate(R.layout.item_coverflow, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
NormalHolder normalHolder = (NormalHolder) holder;
normalHolder.mTV.setText(mDatas.get(position));
normalHolder.mImg.setImageDrawable(mContext.getResources().getDrawable(mPics[position%mPics.length]));
}
@Override
public int getItemCount() {
return mDatas.size();
}
public class NormalHolder extends ViewHolder {
public TextView mTV;
public ImageView mImg;
public NormalHolder(View itemView) {
super(itemView);
mTV = (TextView) itemView.findViewById(R.id.text);
mTV.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mTV.getText(), Toast.LENGTH_SHORT).show();
}
});
mImg = (ImageView)itemView.findViewById(R.id.img);
mImg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mTV.getText(), Toast.LENGTH_SHORT).show();
}
});
}
}
}
代码理解起来应该难度不大,首先,我们新建一个NormalHolder,来保存布局中的控件所对应的变量。然后在onCreateViewHolder
中返回新建的NormalHolder对象,最后通过onBindViewHolder
将NormalHolder与数据绑定起来。
此时运行代码,可以看到效果如下图所示:
因为我们在4.5节中,给每个item在滚动时,都设置了setRotationY,所以我们在滚动时,每个item都还会旋转。我们这节中并不需要让item旋转,所以我们在自定义的LayoutManager中删除child.setRotationY(child.getRotationY() + 1);
代码。
修改后的效果如下图所示:
现在还是4.5节中所实现的竖向滚动,现在我们要把它改为横向滚动。首先,我们需要删除canScrollVertically()
和scrollVerticallyBy
函数,改为:
@Override
public boolean canScrollHorizontally() {
return true;
}
@Override
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
…………
}
在将scrollVerticallyBy
改为scrollHorizontallyBy
以后,需要把原来在scrollVerticallyBy
中的代码移到scrollHorizontallyBy
中来。
很明显,现在运行的话,虽然可以成功运行,但依然是竖向布局。当然是因为我们在onLayoutChildren中,在布局时,并没有将每个item横向布局的原因。
最关键的问题,就是我们在初始化时,会利用mItemRects来保存所有item的位置,所以在计算每个item位置时,改为横向布局的方式来计算即可:
int offsetX = 0;
for (int i = 0; i < getItemCount(); i++) {
Rect rect = new Rect(offsetX, 0, offsetX + mItemWidth, mItemHeight);
mItemRects.put(i, rect);
mHasAttachedItems.put(i, false);
offsetX += mItemWidth;
}
然后在获取visibleCount时,需要修改为:
int visibleCount = getHorizontalSpace() / mItemWidth;
同时,在onLayoutChildren最后,有个计算mTotalHeight的逻辑,我们需要改为计算totalWidth的逻辑:
@Override
public void onLayoutChildren(Recycler recycler, RecyclerView.State state) {
…………
mTotalWidth = Math.max(offsetX, getHorizontalSpace());
}
private int getHorizontalSpace() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
在这段代码中,我们让所有item都靠顶部横向依次排列,难度不大,不再细讲。
同时,在getVisibleArea函数也需要修改,因为我们现在已经是横向滚动了,已经不再是竖向滚动了,所以可见区域应该是横向滚动后的可见区域:
private Rect getVisibleArea() {
Rect result = new Rect(getPaddingLeft() + mSumDx, getPaddingTop(), getWidth() - getPaddingRight() + mSumDx, getHeight()-getPaddingBottom());
return result;
}
onLayoutChildren函数中的其它代码不需要更改,此时onLayoutChildren的代码如下:
public void onLayoutChildren(Recycler recycler, RecyclerView.State state) {
if (getItemCount() == 0) {//没有Item,界面空着吧
detachAndScrapAttachedViews(recycler);
return;
}
mHasAttachedItems.clear();
mItemRects.clear();
detachAndScrapAttachedViews(recycler);
//将item的位置存储起来