RecyclerView系列之——添加header/footer,使用万能adapter,选中滚动居中

如今,Recyclerview的使用已经深入Android开发者的灵魂,一个布局炫不炫,流不流畅就考验开发人员Recyclerview用得6不6。

github代码直通车

先上效果图:


万能adapter效果
点击居中滚动

万能适配器CommonAdpater

本adapter来自鸿洋。首先在gradle添加依赖:
compile 'com.zhy:base-rvadapter:3.0.3'

image.png

它是一个抽象泛型类,需要写入列表显示的实体类。
用法:

        adapter = new CommonAdapter<Bean>(getApplicationContext(), R.layout.item_adapter, datas) {
            @Override
            protected void convert(ViewHolder holder, Bean bean, int position) {
                ImageView imageView = holder.getView(R.id.iv_effectmore_cover);
                holder.setText(R.id.tv_effectmore_title, bean.getName());
                imageView.setImageResource(bean.getCover());

            }
        };

参数与普通adapter用法一样,在convert中,要绑定控件和设置数据。直接holder.getView(resId)传入控件id就可以获取控件。
设置textview文字更简单,holder.setText(resId,string)就可以了。

设置点击和长按事件:

        adapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
                Toast.makeText(getApplicationContext(),"点击第" + position + "个",Toast.LENGTH_SHORT).show();
            }

            @Override
            public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
                Toast.makeText(getApplicationContext(),"长按第" + position + "个",Toast.LENGTH_SHORT).show();
                return false;
            }
        });

选中居中Recyclerview代码如下:

public class ChooseCenterRecyclerview extends RecyclerView{

    private Scroller mScroller;
    private int mLastx = 0;
    private int mTargetPos;
    //用于设置自动平移时候的速度
    private float mPxPerMillsec = 0;

    public ChooseCenterRecyclerview(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context){
        mScroller = new Scroller(context);
    }


    @Override
    public void computeScroll() {
        super.computeScroll();
        //computeScrollOffset返回true表示滚动还在继续,持续时间应该就是startScroll设置的时间
        if(mScroller!=null && mScroller.computeScrollOffset()){
            scrollBy(mLastx - mScroller.getCurrX(), 0);
            mLastx = mScroller.getCurrX();
            postInvalidate();//让系统继续重绘,则会继续重复执行computeScroll
        }
    }

    //调用此方法滚动到目标位置
    public void smoothScrollTo(int fx, int fy,int duration) {
        int dx=0;
        int dy=0;
        if(fx!=0) {
            dx = fx - mScroller.getFinalX();
        }
        if(fy!=0) {
            dy = fy - mScroller.getFinalY();
        }

        smoothScrollBy(dx, dy,duration);
    }

    //调用此方法设置滚动的相对偏移
    public void smoothScrollBy(int dx, int dy,int duration) {
        if(duration>0){
            mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy,duration);
        }else {
            //设置mScroller的滚动偏移量
            mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        }
        /**
         * 重绘整个view,重绘过程会调用到computeScroll()方法
         */
        invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
    }

    public void checkAutoAdjust(int position){
        int childcount = getChildCount();
        //获取可视范围内的选项的头尾位置
        int firstvisiableposition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
        int lastvisiableposition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

        if(position == (firstvisiableposition + 1) || position == firstvisiableposition){
            //当前位置需要向右平移
            leftScrollBy(position, firstvisiableposition);
        }
        else if(position == (lastvisiableposition - 1) || position == lastvisiableposition){
            //当前位置需要向做平移
            rightScrollBy(position, lastvisiableposition);
        }
    }

    private void leftScrollBy(int position, int firstvisiableposition){
        View leftChild = getChildAt(0);
        if(leftChild != null){
            int leftx = leftChild.getLeft();

            int startleft = leftx;
            int endleft = position == firstvisiableposition?leftChild.getWidth():0;

            autoAdjustScroll(startleft, endleft);
        }
    }


    private void rightScrollBy(int position, int lastvisiableposition){
        int childcount = getChildCount();
        View rightChild = getChildAt(childcount - 1);
        if(rightChild != null){
            int rightx = rightChild.getRight();
            int dx = rightx - getWidth();

            int startright = dx;
            int endright = position == lastvisiableposition?-1 * rightChild.getWidth():0;

            autoAdjustScroll(startright, endright);
        }
    }

    /**
     *
     * @param start 滑动起始位置
     * @param end 滑动结束位置
     */
    private void autoAdjustScroll(int start, int end){
        int duration = 0;
        if(mPxPerMillsec != 0){
            duration = (int)((Math.abs(end - start)/mPxPerMillsec));
        }

        mLastx = start;
        if(duration>0) {
            mScroller.startScroll(start, 0, end - start, 0, duration);
        }else{
            mScroller.startScroll(start, 0, end - start, 0);
        }
        postInvalidate();
    }

    /**
     * 将指定item平滑移动到整个view的中间位置
     * @param position
     */
    public void smoothToCenter(int position){
        int parentWidth = getWidth();//获取父视图的宽度
        int childCount = getChildCount();//获取当前视图可见子view的总数
        //获取可视范围内的选项的头尾位置
        int firstvisiableposition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
        int lastvisiableposition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
        int count = ((LinearLayoutManager)getLayoutManager()).getItemCount();//获取item总数

        mTargetPos = Math.max(0, Math.min(count - 1, position));//获取目标item的位置(参考listview中的smoothScrollToPosition方法)

        View targetChild = getChildAt(mTargetPos-firstvisiableposition);//获取目标item在当前可见视图item集合中的位置
        View firstChild = getChildAt(0);//当前可见视图集合中的最左view
        View lastChild = getChildAt(childCount-1);//当前可见视图集合中的最右view

        int childLeftPx = targetChild.getLeft();//子view相对于父view的左边距
        int childRightPx = targetChild.getRight();//子view相对于父view的右边距

        int childWidth = targetChild.getWidth();
        int centerLeft = parentWidth/2-childWidth/2;//计算子view居中后相对于父view的左边距
        int centerRight = parentWidth/2+childWidth/2;//计算子view居中后相对于父view的右边距

        if(childLeftPx>centerLeft){//子view左边距比居中view大(说明子view靠父view的右边,此时需要把子view向左平移
            //平移的起始位置就是子view的左边距,平移的距离就是两者之差
            mLastx = childLeftPx;
            mScroller.startScroll(childLeftPx,0,centerLeft-childLeftPx,0,600);//600为移动时长,可自行设定
            postInvalidate();
        }else if(childRightPx<centerRight){
            mLastx = childRightPx;
            mScroller.startScroll(childRightPx,0,centerRight-childRightPx,0,600);
            postInvalidate();
        }
    }
}

使用方法:

       rvCenter.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if(hasFocus){
                    if(rvCenter.getChildCount() > 0){
                        linearLayoutManager.scrollToPositionWithOffset(0,0);
                        rvCenter.getChildAt(0).requestFocus();
                    }
                }
            }
        });

        adapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
                ...

                rvCenter.smoothToCenter(position);
            }

            @Override
            public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
                return false;
            }
        });

关键需要在item点击事件中设置recyclerview.smoothToCenter()
该recyclerview计算了每次手动滚动距离,根据每个item长度和position,计算每次应该到达的位置,从当前位置.scrollby()按照需要滚动距离到达目标位置;

Recyclerview添加Header和Footer

代码如下:

public class HeaderFooterAdapter extends RecyclerView.Adapter<HeaderFooterAdapter.Holder>{
    private ArrayList<Bean> mDatas;
    private View headerView;
    private View footerView;
    private int typeItem = 0;
    private int typeHeader = 1;
    private int typeFooter = 2;

    public HeaderFooterAdapter(ArrayList<Bean> datas){
        mDatas = datas;
    }

    @Override
    public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(headerView != null && viewType == typeHeader) {
            return new Holder(headerView);
        }

        if(footerView != null && viewType == typeFooter){
            return new Holder(footerView);
        }

        View viewItem = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_headerfooter,null);
        return new Holder(viewItem);

    }

    @Override
    public void onBindViewHolder(Holder holder, int position) {
        if(getItemViewType(position) == typeItem){
            holder.tvTitle.setText(mDatas.get(position - 1).getName());
        }else if(getItemViewType(position) == typeHeader){

        }else if(getItemViewType(position) == typeFooter){

        }
    }

    @Override
    public int getItemViewType(int position) {

        if(headerView == null && footerView == null){
            return typeItem;
        }

        if(position == 0){
            return typeHeader;
        }

        if(position == getItemCount() - 1){
            return typeFooter;
        }

        return typeItem;
    }

    public void addHeader(View headerView){
        this.headerView = headerView;
        notifyItemInserted(0);
    }

    public void addFooter(View footerView){
        this.footerView = footerView;
        notifyItemInserted(getItemCount()-1);
    }

    @Override
    public int getItemCount() {
        int totalCount = mDatas.size();
        if(headerView != null) totalCount+=1;
        if(footerView != null) totalCount+=1;
        return totalCount;
    }

    public class Holder extends RecyclerView.ViewHolder {
        private ImageView imageView;
        private TextView tvTitle;

        public Holder(View itemView) {
            super(itemView);

            if(itemView == headerView){
                return;
            }

            if(itemView == footerView){
                return;
            }

            imageView = itemView.findViewById(R.id.iv_cover);
            tvTitle = itemView.findViewById(R.id.tv_title);
        }
    }
}

用typeHeader,typeFooter,typeItem分别表示布局类型,复写getItemViewType()给当前位置设置类别,在onCreateViewHolder()方法中通过position获取当前itemType来引入不同的布局的Holder。通过设置不同类型type来添加其他布局,采用的是多套布局的思想。不过多套布局是根据实体类的类型来引入不同的layoutId来表示itemtype。多套布局在该系列下一节会讲到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容