当前位置 博文首页 > 启舰:自定义控件三部曲视图篇(九)——RecycerView系列之六实

    启舰:自定义控件三部曲视图篇(九)——RecycerView系列之六实

    作者:[db:作者] 时间:2021-06-30 12:39

    把握生命里的每一分钟,全力以赴我们心中的梦,不经历风雨,怎么见彩虹,没有人能随随便便成功 -----《真心英雄》


    系列文章: Android自定义控件三部曲文章索引: http://blog.csdn.net/harvic880925/article/details/50995268


    本节是我的新书《Android自定义控件高级进阶与精彩实例》中的一小节,目前还在著作中,预计2020年上市,本来没打算更出来,可有些同学评论非常需要这个效果,就摘取出来分享给大家,前面的章节序号我就不改了……(我太懒)

    在上一章中,我们讲了RecyclerView的各种基础知识,在这章中,我们将通过非常炫酷的特效来实际学习下RecyclerView。可以看到,通过这些看似平淡的功能,能做出非常漂亮的控件,现在我们就开始吧。

    5.1 滚动画廊控件

    本节将实现在上一章中提到过的画廊效果,但为了减轻难度,就不再制作3D画廊,而是制作出2D的,不过最后将在2D的基础上,讲解3D画廊的实现原理,本节实现的效果如下图所示:
    在这里插入图片描述

    高能预警:本节代码量较大,而且是利用4.5节代码修改而来,对于同一个函数因为逻辑实现次序的问题,可能会多次修改,而且由于篇幅有限,并不能每次贴出全部源码,只能截取核心部分,所以建议大家对照着源码看本节文章,不然半路有可能会蒙……。

    5.1.1 实现Item布局

    这节内容,我们实现原理与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);代码。

    修改后的效果如下图所示:
    在这里插入图片描述

    5.1.2 实现横向布局

    5.1.2.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横向布局的原因。

    5.1.2.2 实现横向布局

    最关键的问题,就是我们在初始化时,会利用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的位置存储起来