安卓項目實戰之:開源框架BaseRecyclerViewAdapterHelper的使用

添加依賴

1,在Project的build.gradle文件下添加:

allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }

2,在app的build.gradle文件中添加:

dependencies {
            ......
            compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.42'
    }

此處注意雖然GitHub上最新版本爲43,但是引入2.9.43會有問題,因爲該版本是基於AndroidX的。

最基本使用,減少Adapter中70%的代碼

效果如下:
在這裏插入圖片描述
1,先有數據實體模型,即Bean:

public class Model {
    private String title;
    private String content;
    private String imgUrl;

    //生成set、get方法
    ......
}

2,在佈局文件中引入RecycleView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="wrap_content" />
</RelativeLayout>

3,列表中每一項的佈局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_img"
        android:text="我是標題"
        android:textColor="#f00"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@id/iv_img"
        android:text="我是描述" />
</RelativeLayout>

4,編寫適配器Adapter,同時支持重寫onAttachedToRecyclerView方法,通過getSpanSize來實現具有不同尺寸item的動態佈局。

public class MyAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {

    public MyAdapter(@LayoutRes int layoutResId, @Nullable List<Model> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent());
        // 設置圖片
        Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));

        //獲取當前item的position
        //int position = helper.getLayoutPosition();
    }
}

繼承:從上面可以看出我們需要繼承BaseQuickAdapter,其中兩個泛型,第一個泛型Status是數據實體類型,第二個BaseViewHolder是ViewHolder其目的是爲了支持擴展ViewHolder。
賦值:可以直接使用viewHolder對象點相關方法通過傳入viewId和數據進行,方法支持鏈式調用。如果是加載網絡圖片或自定義view可以通過viewHolder.getView(viewId)獲取該控件,如果佈局中包含第三方控件如Banner,那麼也是通過該方式獲取該控件Banner banner = holder.getView(R.id.banner),例如:

    @Override
    protected void convert(BaseViewHolder viewHolder, Status item) {
        viewHolder.setText(R.id.tweetName, item.getUserName())
                .setText(R.id.tweetText, item.getText())
                .setText(R.id.tweetDate, item.getCreatedAt())
                .setVisible(R.id.tweetRT, item.isRetweet())
                .linkify(R.id.tweetText);
                 Banner banner = viewHolder.getView(R.id.banner); // 獲取第三方banner
                 Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) viewHolder.getView(R.id.iv));
    }

5,Activity中代碼:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的數據(實際開發中一般是從網絡獲取的)
        datas = new ArrayList<>();
        Model model;
        for (int i = 0; i < 15; i++) {
            model = new Model();
            model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas.add(model);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MyAdapter(R.layout.item_rv, datas);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}

添加分割線

自定義線性佈局通用的分割線類LinearItemDecoration:

public class LinearItemDecoration extends RecyclerView.ItemDecoration { 

    private Drawable mDivider; 
    private boolean mShowLastLine; 
    private int mSpanSpace = 2; 
    private int mLeftPadding; 
    private int mRightPadding; 
    
    public LinearItemDecoration(int span,int leftPadding,int rightPadding,int color,boolean show){ 
        mSpanSpace = span; 
        mShowLastLine = show; 
        mLeftPadding = leftPadding; 
        mRightPadding = rightPadding; 
        mDivider = new ColorDrawable(color); 
    } 
    
    @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
        int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1; 
        if (isVertical(parent)) { 
            if (parent.getChildAdapterPosition(view) < count) { 
                outRect.set(0, 0, 0, mSpanSpace); 
            } else { 
                outRect.set(0, 0, 0, 0); 
            } 
        } else { 
            if (parent.getChildAdapterPosition(view) < count) { 
                outRect.set(0, 0, mSpanSpace, 0); 
            } else { 
                outRect.set(0, 0, 0, 0); 
            } 
        } 
    } 
    
    private boolean isVertical(RecyclerView parent) { 
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); 
        if (layoutManager instanceof LinearLayoutManager) { 
            int orientation = ((LinearLayoutManager) layoutManager) .getOrientation(); 
            return orientation == LinearLayoutManager.VERTICAL; 
        } 
        return false; 
    } 
            
    @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
        if (isVertical(parent)) { 
            drawVertical(c, parent); 
        } else { 
            drawHorizontal(c, parent); 
        } 
    } 
    
    private void drawVertical(Canvas c, RecyclerView parent) { 
        final int left = parent.getPaddingLeft() + mLeftPadding; 
        final int right = parent.getWidth() - parent.getPaddingRight() - mRightPadding; 
        final int childCount = parent.getChildCount(); 
        for (int i = 0; i < childCount; i++) { 
            final View child = parent.getChildAt(i); 
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); 
            final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child)); 
            final int bottom = top + mSpanSpace; int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1; 
            if (i < count) { 
                mDivider.setBounds(left, top, right, bottom); 
                mDivider.draw(c); 
            } else { 
                mDivider.setBounds(left, top, right, top); 
                mDivider.draw(c); 
            } 
        }
    } 
    
    private void drawHorizontal(Canvas c, RecyclerView parent) { 
        final int top = parent.getPaddingTop(); 
        final int bottom = parent.getHeight() - parent.getPaddingBottom(); 
        final int childCount = parent.getChildCount(); 
        for (int i = 0; i < childCount; i++) { 
            final View child = parent.getChildAt(i); 
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); 
            final int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child)); 
            final int right = left + mSpanSpace; int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1; 
            if (i < count) { 
                mDivider.setBounds(left, top, right, bottom); 
                mDivider.draw(c); 
            } 
        } 
    } 
    
    /** 
    * Builder模式 
    * */ 
    public static class Builder{ 
    
        private Context mContext; 
        private Resources mResources; 
        private int mSpanSpace; 
        private boolean mShowLastLine; 
        private int mLeftPadding; 
        private int mRightPadding; 
        private int mColor; 
        
        public Builder(Context context){ 
            mContext = context; 
            mResources = context.getResources(); 
            mSpanSpace = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 1f, context.getResources().getDisplayMetrics()); 
            mLeftPadding = 0; 
            mRightPadding = 0; 
            mShowLastLine = false; 
            mColor = Color.BLACK; 
        } 
        
        /** 
        * 設置分割線寬(高)度 
        */ 
        public Builder setSpan(float pixels) { 
            mSpanSpace = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixels, mResources.getDisplayMetrics()); 
            return this;        
        } 
        
        /** 
        * 設置分割線寬(高)度 
        */ 
        public Builder setSpan(@DimenRes int resource) { 
            mSpanSpace = mResources.getDimensionPixelSize(resource); 
            return this; 
        } 
        
        /** 
        * 設置左右間距 
        */ 
        public Builder setPadding(float pixels) { 
            setLeftPadding(pixels); 
            setRightPadding(pixels); 
            return this; 
        } 
        
        /** 
        * 設置左右間距 
        */ 
        public Builder setPadding(@DimenRes int resource) { 
            setLeftPadding(resource); 
            setRightPadding(resource); 
            return this; 
        } 
        
        /** 
        * 設置左間距 
        */ 
        public Builder setLeftPadding(float pixelPadding) { 
            mLeftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixelPadding, mResources.getDisplayMetrics()); 
            return this; 
        } 
        
        /** 
        * 設置右間距 
        */ 
        public Builder setRightPadding(float pixelPadding) { 
            mRightPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixelPadding, mResources.getDisplayMetrics()); 
            return this; 
        } 
        
        /** 
        * 通過資源id設置左間距 
        */ 
        public Builder setLeftPadding(@DimenRes int resource) { 
            mLeftPadding = mResources.getDimensionPixelSize(resource); 
            return this; 
        } 
        
        /** 
        * 通過資源id設置右間距 
        */ 
        public Builder setRightPadding(@DimenRes int resource) { 
            mRightPadding = mResources.getDimensionPixelSize(resource); 
            return this; 
        } 
        
        /** 
        * 通過資源id設置顏色 
        */ 
        public Builder setColorResource(@ColorRes int resource) { 
            setColor(ContextCompat.getColor(mContext,resource)); 
            return this; 
        } 
        
        /** 
        * 設置顏色 
        */ 
        public Builder setColor(@ColorInt int color) { 
            mColor = color; 
            return this; 
        } 
        
        /** 
        * 是否最後一條顯示分割線 
        * */ 
        public Builder setShowLastLine(boolean show){ 
            mShowLastLine = show; 
            return this; 
        } 
        
        /** 
        * Instantiates a LinearItemDecoration with the specified parameters. 
        * @return a properly initialized LinearItemDecoration instance 
        */ 
        public LinearItemDecoration build() { 
            return new LinearItemDecoration(mSpanSpace,mLeftPadding,mRightPadding,mColor,mShowLastLine); 
        }    
    }   
}

使用如下:

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context); 
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 
/**設置recyclerview*/ 
LinearItemDecoration divider = new LinearItemDecoration.Builder(context) 
                                .setSpan(20f)
//                              .setPadding(R.dimen.line_width) 
//                              .setLeftPadding(R.dimen.common_title_height)
//                              .setRightPadding(R.dimen.common_title_height) 
                                .setColorResource(R.color.gray_line) 
                                .setShowLastLine(true) 
                                .build(); 
recycle_recommend.addItemDecoration(divider); 
recycle_recommend.setLayoutManager(linearLayoutManager);

自定義網格佈局通用的分割線GridItemDecoration類,代碼如下:

public class GridItemDecoration extends RecyclerView.ItemDecoration {

    private Drawable mDivider;
    private boolean mShowLastLine;
    private int mHorizonSpan;
    private int mVerticalSpan;

    private GridItemDecoration(int horizonSpan,int verticalSpan,int color,boolean showLastLine) {
        this.mHorizonSpan = horizonSpan;
        this.mShowLastLine = showLastLine;
        this.mVerticalSpan = verticalSpan;
        mDivider = new ColorDrawable(color);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawHorizontal(c, parent);
        drawVertical(c, parent);
    }

    private void drawHorizontal(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            //最後一行底部橫線不繪製
            if (isLastRaw(parent,i,getSpanCount(parent),childCount) && !mShowLastLine){
                continue;
            }
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = child.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin;
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mHorizonSpan;

            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    private void drawVertical(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            if((parent.getChildViewHolder(child).getAdapterPosition() + 1) % getSpanCount(parent) == 0){
                continue;
            }
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int top = child.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin + mHorizonSpan;
            final int left = child.getRight() + params.rightMargin;
            int right = left + mVerticalSpan;
//            //滿足條件( 最後一行 && 不繪製 ) 將vertical多出的一部分去掉;
            if (i==childCount-1) {
                right -= mVerticalSpan;
            }
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    /**
     * 計算偏移量
     * */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();

        if (itemPosition < 0){
            return;
        }

        int column = itemPosition % spanCount;
        int bottom;

        int left = column * mVerticalSpan / spanCount;
        int right = mVerticalSpan - (column + 1) * mVerticalSpan / spanCount;

        if (isLastRaw(parent, itemPosition, spanCount, childCount)){
            if (mShowLastLine){
                bottom = mHorizonSpan;
            }else{
                bottom = 0;
            }
        }else{
            bottom = mHorizonSpan;
        }
        outRect.set(left, 0, right, bottom);
    }

    /**
     * 獲取列數
     * */
    private int getSpanCount(RecyclerView parent) {
        // 列數
        int mSpanCount = -1;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            mSpanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            mSpanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
        }
        return mSpanCount;
    }

    /**
     * 是否最後一行
     * @param parent     RecyclerView
     * @param pos        當前item的位置
     * @param spanCount  每行顯示的item個數
     * @param childCount child個數
     * */
    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();

        if (layoutManager instanceof GridLayoutManager) {
            return getResult(pos,spanCount,childCount);
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                // StaggeredGridLayoutManager 且縱向滾動
                return getResult(pos,spanCount,childCount);
            } else {
                // StaggeredGridLayoutManager 且橫向滾動
                if ((pos + 1) % spanCount == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean getResult(int pos,int spanCount,int childCount){
        int remainCount = childCount % spanCount;//獲取餘數
        //如果正好最後一行完整;
        if (remainCount == 0){
            if(pos >= childCount - spanCount){
                return true; //最後一行全部不繪製;
            }
        }else{
            if (pos >= childCount - childCount % spanCount){
                return true;
            }
        }
        return false;
    }

    /**
     * 使用Builder構造
     * */
    public static class Builder {
        private Context mContext;
        private Resources mResources;
        private boolean mShowLastLine;
        private int mHorizonSpan;
        private int mVerticalSpan;
        private int mColor;

        public Builder(Context context) {
            mContext = context;
            mResources = context.getResources();
            mShowLastLine = true;
            mHorizonSpan = 0;
            mVerticalSpan = 0;
            mColor = Color.WHITE;
        }

        /**
         * 通過資源文件設置分隔線顏色
         */
        public Builder setColorResource(@ColorRes int resource) {
            setColor(ContextCompat.getColor(mContext, resource));
            return this;
        }

        /**
         * 設置顏色
         */
        public Builder setColor(@ColorInt int color) {
            mColor = color;
            return this;
        }

        /**
         * 通過dp設置垂直間距
         * */
        public Builder setVerticalSpan(@DimenRes int vertical) {
            this.mVerticalSpan = mResources.getDimensionPixelSize(vertical);
            return this;
        }

        /**
         * 通過px設置垂直間距
         * */
        public Builder setVerticalSpan(float mVertical) {
            this.mVerticalSpan = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mVertical, mResources.getDisplayMetrics());
            return this;
        }

        /**
         * 通過dp設置水平間距
         * */
        public Builder setHorizontalSpan(@DimenRes int horizontal) {
            this.mHorizonSpan = mResources.getDimensionPixelSize(horizontal);
            return this;
        }

        /**
         * 通過px設置水平間距
         * */
        public Builder setHorizontalSpan(float horizontal) {
            this.mHorizonSpan = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, horizontal, mResources.getDisplayMetrics());
            return this;
        }

        /**
         * 是否最後一條顯示分割線
         * */
        public GridItemDecoration.Builder setShowLastLine(boolean show){
            mShowLastLine = show;
            return this;
        }

        public GridItemDecoration build() {
            return new GridItemDecoration(mHorizonSpan, mVerticalSpan, mColor,mShowLastLine);
        }
    }
}

使用如下:

        //創建佈局管理
        GridLayoutManager manager = new GridLayoutManager(mActivity,3,GridLayoutManager.VERTICAL,false);
        GridItemDecoration divider = new GridItemDecoration.Builder(mActivity)
                .setHorizontalSpan(R.dimen.common_vew_column_padding)
                .setVerticalSpan(R.dimen.common_vew_raw_padding)
                .setColorResource(R.color.dark_grey)  // 線的顏色
                .setShowLastLine(false) // 是否顯示最後一行的線
                .build();
        recyclerView.addItemDecoration(divider);

注意:setHorizontalSpan和setVerticalSpan用於控制線在不同方向上的寬度,必須制定一個尺寸,不然線顯示不出來。

添加點擊事件監聽

1,條目item的點擊事件和長按事件:

//條目點擊事件
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
             Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
        }
});
//條目長按事件
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
       @Override
       public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
             Toast.makeText(MainActivity.this, "長按了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
              return false;
       }
});

長按事件返回false,事件會繼續向下傳遞觸發點擊事件,如果返回true,則不會觸發點擊事件。
注意:在嵌套recycleView的情況下需要使用 adapter. setOnItemClickListener 來設置點擊事件,如果使用recycleView.addOnItemTouchListener會累計添加的。

2,item子控件的點擊事件和長按事件
首先需要在adapter的convert方法裏面通過 helper.addOnClickListener 綁定一下子控件的控件id:

@Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //給圖標添加點擊事件
                .addOnLongClickListener(R.id.tv_title)//給標題添加長按事件
                .addOnClickListener(R.id.tv_content);  //給內容也添加點擊事件
        // 設置圖片
        Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));
    }

然後設置:

//條目子控件點擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
       @Override
       public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
            Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
       }
});

或者設置:

//條目子控件長按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
      @Override
      public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
          Toast.makeText(MainActivity.this, "長按了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
          return false;
      }
});

不管是設置item子控件的點擊事件還是長按事件都需要先在adapter的convert方法中綁定,否則沒有效果。

3,item中同時有多個子控件添加了點擊事件
當然首先也需要在adapter的convert方法中綁定事件,然後作如下判斷即可:

//條目子控件點擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
      @Override
      public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
            //判斷id
            if (view.getId() == R.id.iv_img) {
                  Log.i("tag", "點擊了第" + position + "條條目的 圖片");
             } else if (view.getId() == R.id.tv_title) {
                   Log.i("tag", "點擊了第" + position + "條條目的 標題");
              }
       }
});

長按事件也是相同的方法處理。

在子控件事件中獲取其他子控件

//條目子控件點擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
       @Override
       public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
             Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
             // 獲取標題控件
             TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
             Log.i("tag", "當前圖片對應的 title=" + tv_title.getText());
       }
});

在子控件點擊事件中獲取其他子控件時,需要注意position的取值,如果有header的話需要處理一下position加上 headerlayoutcount。

添加列表加載動畫

設置開啓動畫,默認爲漸顯效果:

// 這樣設置默認效果爲:漸顯效果
adapter.openLoadAnimation();

該適配器提供了5種動畫效果(漸顯、縮放、從下到上,從左到右、從右到左):

public static final int ALPHAIN = 0x00000001;           //漸顯
public static final int SCALEIN = 0x00000002;           //縮放
public static final int SLIDEIN_BOTTOM = 0x00000003;    //從下到上
public static final int SLIDEIN_LEFT = 0x00000004;      //從左到右
public static final int SLIDEIN_RIGHT = 0x00000005;     //從右到左

如果不想使用默認動畫效果,我們可以這樣來指定需要的效果:

//使用縮放動畫
adapter.openLoadAnimation(BaseQuickAdapter.SCALEIN);

如果想自定義動畫效果,可以這樣做:

//自定義動畫效果
adapter.openLoadAnimation(new BaseAnimation() {
       @Override
       public Animator[] getAnimators(View view) {
              return new Animator[]{
                    ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5f, 1),
                     ObjectAnimator.ofFloat(view, "scaleX", 1, 0.5f, 1)
               };
        }
});

默認動畫效果只執行一次,如果想重複執行,可設置:

//設置重複執行動畫
adapter.isFirstOnly(false);

設置不顯示動畫數量(目前還沒測出效果):
由於進入界面的item都是很多的速度進來的所以不會出現滑動顯示的依次執行動畫效果,這個時候會一起執行動畫,如果覺得這樣的效果不好可以使用setNotDoAnimationCount設置第一屏item不執行動畫:

adapter.setNotDoAnimationCount(count);

添加頭部丶尾部

添加(可多次添加):

mQuickAdapter.addHeaderView(headerView);
mQuickAdapter.addFooterView(footerView);

刪除指定View:

mQuickAdapter.removeHeaderView(headerView);
mQuickAdapter.removeFooterView(footerView);

刪除所有:

mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();

默認出現了頭部就不會顯示Empty,和尾部,配置以下方法也支持同時顯示:

mQuickAdapter.setHeaderAndEmpty()
mQuickAdapter.setHeaderFooterEmpty();

默認頭部尾部都是佔滿一行,如果需要不佔滿可以配置:

mQuickAdapter.setHeaderViewAsFlow();
mQuickAdapter.setFooterViewAsFlow();

如果需要給頭部的控件添加點擊事件監聽,如下:

View inflate = View.inflate(mActivity, R.layout.table_oupei_titledz, null);
initHead(inflate); // 頭部控件獲取及初始化
adapter.addHeaderView(inflate); // 添加頭
rvShow.setAdapter(adapter);

...

// 爲recyclerView的頭部設置點擊事件監聽
    private void initHead(View inflate) {
        RoundTextView dingzhi = inflate.findViewById(R.id.dingzhi);
        dingzhi.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ToastUtil.showShort(mActivity,"點擊了定製...");
            }
        });
    }

上拉加載更多

默認上拉加載功能就是開啓的,當我們第一頁展示沒有佔滿一屏時,我們會發現剛開始在加載第一頁的數據時會同時展示正在加載更多視圖然後自動加載更多數據,官網上說:默認第一次加載會進入加載更多的回調,如果不需要可以配置:

adapter.disableLoadMoreIfNotFullPage();

但是設置了之後程序出現閃退,查看日誌說是必須先調用bindToRecyclerView方法,於是設置如下:

adapter.bindToRecyclerView(recyclerView);
adapter.disableLoadMoreIfNotFullPage();

此時發現不閃退了,但是當沒有佔滿一屏時,第一次進去還是默認會顯示加載視圖然後自動加載更多數據,本來初衷是想去掉默認的加載動作的,但是發現並沒有起作用,此處留給讀者自己去研究。

上拉加載監聽的回調,(模擬數據獲取,完整代碼如下):

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private List<Model> datas2;
    private MyAdapter adapter;
    private int TOTAL_COUNTER = 30;   // 總的數據
    private int mCurrentCounter = 15; // 每頁顯示的條數
    private boolean isErr = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的數據(實際開發中一般是從網絡獲取的)
        datas = new ArrayList<>();
        datas2 = new ArrayList<>();
        Model model;
        for (int i = 0; i < 15; i++) {
            model = new Model();
            model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas.add(model);
        }
        Model model2;
        for (int i = 15; i < 30; i++) {
            model2 = new Model();
            model2.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
            model2.setTitle("我是第" + i + "條標題");
            model2.setContent("第" + i + "條內容");
            datas2.add(model2);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MyAdapter(R.layout.item, datas);

//        adapter.setLoadMoreView(new CustomLoadMoreView());

          // 並未起作用
//        adapter.bindToRecyclerView(recyclerView);
//        adapter.disableLoadMoreIfNotFullPage();

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);



        //上拉加載(設置這個監聽就表示有上拉加載功能了)
        adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override public void onLoadMoreRequested() {
                recyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            adapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據(可以直接往適配器添加數據)
                                // adapter.addData(DataServer.getSampleData(PAGE_SIZE));
                                adapter.addData(datas2);
                                mCurrentCounter = adapter.getData().size();
                                //主動調用加載完成,停止加載
                                adapter.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(MainActivity.this, "加載失敗!", Toast.LENGTH_LONG).show();
                                //同理,加載失敗也要主動調用加載失敗來停止加載(而且該方法會提示加載失敗)
                                adapter.loadMoreFail();

                            }
                        }
                    }

                }, 5000);
            }
        }, recyclerView);

    }
}

從上面例子我們可以看出:
1,數據加載完成,加載失敗,加載結束時都必須分別調用一次adapter的方法告訴系統,如下:
加載完成(表示本頁數據加載完成,還有下頁數據):

adapter.loadMoreComplete();

加載失敗:

adapter.loadMoreFail();

加載結束(表示已無更多數據):

adapter.loadMoreEnd();

2,如果上拉結束後,下拉刷新需要再次開啓上拉監聽,需要使用setNewData方法填充數據。

打開或關閉加載(一般用於下拉的時候做處理,因爲上拉下拉不能同時操作)

adapter.setEnableLoadMore(boolean);

預加載

// 當列表滑動到倒數第N個Item的時候(默認是1)回調onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);

自定義加載更多佈局樣式:包括加載中,加載失敗,無更多數據等的視圖樣式

adapter.setLoadMoreView(new CustomLoadMoreView());

繼承LoadMoreView實現自定義的CustomLoadMoreView:

public class CustomLoadMoreView extends LoadMoreView {

    @Override
    public int getLayoutId() {
        // 該佈局文件中同時指定了三種情況下的視圖樣式
        return R.layout.view_load_more;
    }

    /**
     * 如果返回true,數據全部加載完畢後會隱藏加載更多
     * 如果返回false,數據全部加載完畢後會顯示getLoadEndViewId()指定的佈局
     */
    @Override
    public boolean isLoadEndGone() {
        return false;
    }

    @Override
    protected int getLoadingViewId() {
        // 指定加載更多時的視圖樣式
        return R.id.load_more_loading_view;
    }

    @Override
    protected int getLoadFailViewId() {
        // 指定加載更多失敗時的視圖樣式
        return R.id.load_more_load_fail_view;
    }

    /**
     * isLoadEndGone()爲true,可以返回0
     * isLoadEndGone()爲false,不能返回0
     */
    @Override
    protected int getLoadEndViewId() {
//        return 0;
        // 指定沒有更多數據時的視圖樣式
        return R.id.load_more_load_fail_view;
    }
}

可以看到在上面的類中要求我們必須定義一個同時包含三種情況下視圖樣式的佈局文件view_load_more.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="40dp">


    <!--加載中視圖樣式-->
    <LinearLayout
        android:id="@+id/load_more_loading_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">
        <ProgressBar
            android:id="@+id/loading_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_marginRight="@dimen/4dp"
            />
        <TextView
            android:id="@+id/loading_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/4dp"
            android:text="正在加載更多..."
            android:textColor="#0dddb8"
            android:textSize="14sp"/>
    </LinearLayout>


    <!--加載失敗試圖樣式-->
    <FrameLayout
        android:id="@+id/load_more_load_fail_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">
        <TextView
            android:id="@+id/tv_prompt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="#0dddb8"
            android:text="加載失敗!"/>
    </FrameLayout>


    <!--已無更多數據的試圖樣式-->
    <RelativeLayout
        android:id="@+id/load_more_no_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="@color/red"
            android:text="已無更多數據!"/>
    </RelativeLayout>
    
</FrameLayout>

在我們自定義的CustomLoadMoreView類中,每種方法返回的就是佈局文件中指定的對應情況下試圖樣式的樣式id值。
建議:自定義佈局文件時,根佈局使用FrameLayout不要變。

下拉刷新

該框架實現的下拉刷新功能跟我們通常的下拉刷新是有區別的,類似於查看qq聊天記錄時的歷史記錄查看效果,不是app中那種常見的下拉刷新,因此對於實現下拉刷新效果的實現,我一般會選擇使用SwipeRefreshLayout或者智能控件SmartRefreshLayout來實現,SmartRefreshLayout還是很強大的,關於SmartRefreshLayout的使用不會的讀者可查看我的博客:安卓項目實戰之強大的智能下拉刷新上拉加載框架SmartRefreshLayout 來了解。
此處要注意的是:
在上面講解上拉加載更多時我們說過:
如果上拉加載結束後,下拉刷新需要再次開啓上拉加載監聽,需要使用setNewData方法填充數據。

swip.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                adapter.setNewData(datas3);
                mCurrentCounter = 15;
                Toast.makeText(MainActivity.this, "下拉刷新了!", Toast.LENGTH_SHORT).show();
                //刷新完成
                swip.setRefreshing(false);
            }
        });

分組佈局

官網上關於這部分的講解,示例代碼用的還是之前版本的api,如果讀者想實現該效果,此處推薦一篇博客:https://blog.csdn.net/mores_zixuan/article/details/78855601,
裏面有具體的實現思路,可提供參考。

多類型佈局

作爲項目中最常使用的佈局形式,該適配器提供了兩種方案來實現多類型的佈局效果:
1,實現MultiItemEntity的方式
2,爲 BaseQuickAdapter 設置代理的方式
下面我們分別來介紹兩種方式的具體實現過程。

方式一:實體類實現MultiItemEntity接口的方式
1,實體類必須實現MultiItemEntity,重寫getItemType方法:

public class MyMultipleItem implements MultiItemEntity {

    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    private int itemType;
    private Model data;

    public MyMultipleItem(int itemType, Model data) {
        this.itemType = itemType;
        this.data = data;
    }

    @Override
    public int getItemType() {
        return itemType;
    }

    public Model getData(){
        return data;
    }
}

2,創建適配器,在adapter中綁定type和layout之間的關係,然後convert方法中爲不同的layout設置數據時,通過判斷樣式爲不同情況下的不同控件設置不同的數據:

public class MultipleItemAdapter extends BaseMultiItemQuickAdapter<MyMultipleItem, BaseViewHolder> {

    public MultipleItemAdapter(List data) {
        super(data);
        //必須綁定type和layout的關係
        addItemType(MyMultipleItem.FIRST_TYPE, R.layout.first_type_layout);
        addItemType(MyMultipleItem.SECOND_TYPE, R.layout.second_type_layout);
        addItemType(MyMultipleItem.NORMAL_TYPE, R.layout.item_rv);

    }

    @Override
    protected void convert(BaseViewHolder helper, MyMultipleItem item) {
        switch (helper.getItemViewType()) {
            case MyMultipleItem.FIRST_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getData().getTitle())
                        .setText(R.id.tv_content, item.getData().getContent());
                break;
            case MyMultipleItem.SECOND_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getData().getTitle())
                        .setText(R.id.tv_content, item.getData().getContent());
                break;
            case MyMultipleItem.NORMAL_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getData().getTitle())
                        .setText(R.id.tv_content, item.getData().getContent());
                break;
        }
    }
}

3,設置不同的佈局文件樣式
second_type_layout.xml佈局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_img"

        android:text="我是標題"
        android:textColor="#00ff00"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:textColor="#00ff00"
        android:layout_toRightOf="@id/iv_img"
        android:text="我是描述" />
</RelativeLayout>

爲了簡單期間我們另外兩種佈局只改變標題和內容文字的顏色來區分:
first_type_layout.xml:藍色
second_type_layout.xml:綠色
item_rv.xml:紅色

4,Activity中代碼:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas01;
    private List<MyMultipleItem> datas02;
    private MultipleItemAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的假數據(實際開發中當然是從網絡獲取數據)
        datas01 = new ArrayList<>();
        Model model;
        for (int i = 0; i < 30; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas01.add(model);
        }

        datas02 = new ArrayList<>();
        //這裏我是隨機給某一條目加載不同的佈局
        for (int i = 0; i < 30; i++) {
            if (i % 3 == 0) {
                // 藍色:0,3,6,9,12,15,18....
                datas02.add(new MyMultipleItem(MyMultipleItem.FIRST_TYPE, datas01.get(i)));
            } else if (i % 4 == 0) {
                // 綠色: 4,8,12,16,20,24....
                datas02.add(new MyMultipleItem(MyMultipleItem.SECOND_TYPE, datas01.get(i)));
            } else {
                // 紅色: 其餘的全是紅色
                datas02.add(new MyMultipleItem(MyMultipleItem.NORMAL_TYPE, datas01.get(i)));
            }

        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MultipleItemAdapter(datas02);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}

經過上面三步我們就已經實現了多類型的佈局效果了,效果如圖:
在這裏插入圖片描述
實際項目中場景:
上面的方式我們是在Activity中手動指定了對應position位置應該顯式什麼樣的佈局樣式,但是一般的項目中很少會有這種情況,一般都是需要根據實體類中某個屬性的值去動態判斷需要顯示哪種佈局,接下來我們就演示一下這種情況:

1,對應的實體類如下,我們通過判斷Type字段的值來和不同的佈局相對應,比如我之前項目中某個頁面要實現多類型佈局效果,包括沒有圖片,一張圖片和三張圖片三種情況,每種情況對應不同的顯示效果,其中imageUrl字段返回的是一個字符串數組,我當時就是通過判斷該數組的長度返回不同的type的,此處模擬的相對簡單點,直接判斷type值:

public class Model2 implements MultiItemEntity{

    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    private String title;
    private String content;
    private String imgUrl;
    private int type;

    @Override
    public int getItemType() {
        if(type == 1){
            return FIRST_TYPE;    // 藍色
        }else if(type == 2){
            return SECOND_TYPE;   // 綠色
        }else{
            return NORMAL_TYPE;   // 紅色
        }
    }


    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}

2,適配器和三種佈局文件的代碼保持不變,接下來我們來看activity的代碼:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model2> data;
    private MultipleItemAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的假數據(實際開發中當然是從網絡獲取數據)
        data = new ArrayList<>();
        Model2 model2;
        for (int i = 0; i < 30; i++) {
            model2 = new Model2();
            model2.setTitle("我是第" + i + "條標題");
            model2.setContent("第" + i + "條內容");
            // 根據不同position設置不同的type值去加載不同的視圖,模擬的是網絡數據指定屬性的值動態變化的場景
            if (i % 3 == 0) {
                // 藍色:0,3,6,9,12,15,18....
                model2.setType(1);
            } else if (i % 4 == 0) {
                // 綠色: 4,8,12,16,20,24....
                model2.setType(2);
            } else {
                // 紅色: 其餘的全是紅色
                model2.setType(3);
            }
            data.add(model2);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MultipleItemAdapter(data);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}

這樣就實現了根據從網絡獲取到的實體數據去動態的判斷需要使用哪種佈局去展示,我們也明白其實是對type的字段進行了動態判斷,運行的效果和第一種情況運行的效果是一致的,因爲在設置type的值時,我們就是以第一種方式實現的效果來設置的position和type取值之間的關係,當然爲了驗證你也可以改變該規則。

方式二:爲 BaseQuickAdapter 設置代理的方式

從第一種實現方式中我們可以知道,其實多佈局的本質還是要用某一個變量來區分,上述的方法是使用專門提供得MultiItemEntity接口,讓我們實現,從而進行區分,既然是通過某一個變量來區分,那我們能不能不實現MultiItemEntity接口,直接在適配器裏進行區分呢?
答案是可以的,官方還給出了一種方式,就是我們接下來要說的,給BaseQuickAdapter 設置代理的方式:
1,定義實體類還是通過type字段判斷,只不過此時不用實現MultiItemEntity接口,也就不用重寫getItemType方法,之前getItemType方法中通過type判斷佈局樣式的邏輯代碼就放在adapter中完成了,具體看下面adapter中代碼:

public class Model2 {

    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    private String title;
    private String content;
    private String imgUrl;
    private int type;
    

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

}

2,Adapter中代碼,我們可以很明顯發現之前寫在實體類Model2中的判斷邏輯代碼此時全部寫在了Adapter中:
這種方式實現adapter分三步,在下面也標出來了。

public class MultipleItemAdapter extends BaseQuickAdapter<Model2, BaseViewHolder> {

    public MultipleItemAdapter(@Nullable List<Model2> data) {
        super(data);
        // 第一步:動態判斷
        setMultiTypeDelegate(new MultiTypeDelegate<Model2>() {
            @Override
            protected int getItemType(Model2 entity) {
                //根據你的實體類來判斷佈局類型
                int type = entity.getType();
                if(type == 1){
                    return FIRST_TYPE;    // 藍色
                }else if(type == 2){
                    return SECOND_TYPE;   // 綠色
                }else{
                    return NORMAL_TYPE;   // 紅色
                }
            }
        });

        // 第二步:設置type和layout的對應關係
        getMultiTypeDelegate()
                .registerItemType(FIRST_TYPE, R.layout.first_type_layout)
                .registerItemType(SECOND_TYPE, R.layout.second_type_layout)
                .registerItemType(NORMAL_TYPE, R.layout.item_rv);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model2 item) {
        // 第三步:設置不同佈局下的組件數據
        switch (helper.getItemViewType()) {
            case FIRST_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getTitle())
                        .setText(R.id.tv_content, item.getContent());
                break;
            case SECOND_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getTitle())
                        .setText(R.id.tv_content, item.getContent());
                break;
            case NORMAL_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getTitle())
                        .setText(R.id.tv_content, item.getContent());
                break;
        }
    }
}

3,Activity中代碼和第一種方式實際項目例子中那個保持不變:

public class GroupActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model2> data;
    private MultipleItemAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的假數據(實際開發中當然是從網絡獲取數據)
        data = new ArrayList<>();
        Model2 model2;
        for (int i = 0; i < 30; i++) {
            model2 = new Model2();
            model2.setTitle("我是第" + i + "條標題");
            model2.setContent("第" + i + "條內容");
            // 根據不同position設置不同的type值去加載不同的視圖,模擬的是網絡數據指定屬性的值動態變化的場景
            if (i % 3 == 0) {
                // 藍色:0,3,6,9,12,15,18....
                model2.setType(1);
            } else if (i % 4 == 0) {
                // 綠色: 4,8,12,16,20,24....
                model2.setType(2);
            } else {
                // 紅色: 其餘的全是紅色
                model2.setType(3);
            }
            data.add(model2);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MultipleItemAdapter(data);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }

}

總結:可以發現兩種實現方式最主要的區別就是將判斷type的邏輯代碼一個放在了實體類中,一個放在了Adapter中,第二種方式最終運行的效果和前面也是一致的。
至此兩種方式設置多佈局的內容就講解完了。

設置空佈局

沒有數據時就默認顯示該佈局:

// 沒有數據的時候默認顯示該佈局
adapter.setEmptyView(View.inflate(this,R.layout.load_empty,null));

添加拖拽丶滑動刪除

拖拽和滑動刪除的回調方法:

// 拖拽
OnItemDragListener onItemDragListener = new OnItemDragListener() {
    @Override
    public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
    @Override
    public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
    @Override
    public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
}

// 滑動刪除
OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
    @Override
    public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
};

adapter需要繼承BaseItemDraggableAdapter:

public class MyAdapter extends BaseItemDraggableAdapter<Model, BaseViewHolder> {

    public MyAdapter(int layoutResId, List<Model> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //給圖標添加點擊事件
                .addOnLongClickListener(R.id.tv_title)//給標題添加長按事件
                .addOnClickListener(R.id.tv_content);  //給內容也添加點擊事件
        // 設置圖片
        Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));
    }
}

adapter跟之前的adapter的區別就是繼承的接口類名不一樣,類體中代碼不需要更改,包括構造方法也不需要修改。

Activity中代碼:

mAdapter = new ItemDragAdapter(mData);

ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);

// 開啓拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);

// 開啓滑動刪除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);

注意:默認不支持多個不同的 ViewType 之間進行拖拽,如果開發者有所需求:重寫 ItemDragAndSwipeCallback 裏的onMove()方法,return true即可。
完整activity代碼如下:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private List<Model> datas2;
    private MyAdapter adapter;
    private int TOTAL_COUNTER = 30;   // 總的數據
    private int mCurrentCounter = 15; // 每頁顯示的條數
    private boolean isErr = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的數據(實際開發中一般是從網絡獲取的)
        datas = new ArrayList<>();
        datas2 = new ArrayList<>();
        Model model;
        for (int i = 0; i < 15; i++) {
            model = new Model();
            model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas.add(model);
        }
        Model model2;
        for (int i = 15; i < 30; i++) {
            model2 = new Model();
            model2.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
            model2.setTitle("我是第" + i + "條標題");
            model2.setContent("第" + i + "條內容");
            datas2.add(model2);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器,參數1爲條目佈局
        adapter = new MyAdapter(R.layout.item,datas);

        ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(adapter);
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);

        adapter.setLoadMoreView(new CustomLoadMoreView());


        // 拖拽
        OnItemDragListener onItemDragListener = new OnItemDragListener() {
            @Override
            public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
            @Override
            public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
            @Override
            public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
        };

        // 滑動刪除
        OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {

            @Override
            public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {

            }

            @Override
            public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {

            }

            @Override
            public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {

            }

            @Override
            public void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive) {

            }
        };



        // 開啓拖拽
        adapter.enableDragItem(itemTouchHelper, R.id.tv_title, true);
        adapter.setOnItemDragListener(onItemDragListener);

        // 開啓滑動刪除
        adapter.enableSwipeItem();
        adapter.setOnItemSwipeListener(onItemSwipeListener);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);

        //上拉加載(設置這個監聽就表示有上拉加載功能了)
        adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override public void onLoadMoreRequested() {
                recyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            adapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據(可以直接往適配器添加數據)
                                adapter.addData(datas2);
                                mCurrentCounter = adapter.getData().size();
                                //主動調用加載完成,停止加載
                                adapter.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(MainActivity.this, "加載失敗!", Toast.LENGTH_LONG).show();
                                //同理,加載失敗也要主動調用加載失敗來停止加載(而且該方法會提示加載失敗)
                                adapter.loadMoreFail();

                            }
                        }
                    }

                }, 5000);
            }
        }, recyclerView);

    }
}

注意:上面開啓拖拽時:

adapter.enableDragItem(itemTouchHelper, R.id.tv_title, true);

參數2爲一個int類型的值,此處我設置的是每一個條目中的標題組件的id,在測試時發現只有長按每個item的標題才能實現拖拽,長按其他位置,如圖片,內容,空白處都不可以,所以一般將該參數值設置爲條目item的根佈局id,我們需要去給item佈局文件中的根佈局設置一個id,然後此處設置爲該id,經測試,此時長按條目空白處可以實現拖拽,但是長按標題,內容,圖片等都沒有反應,所以該參數的值就是指定長按那個組件來觸發拖拽。

列表和網格佈局的切換

private boolean flag;
    public void change(View view){
        if(!flag){
            // 網格
            setGvAdapter();
            flag = true;
        }else{
            setLvAdapter();
            flag = false;
        }
        // 定位到上次切換的位置
//        recyclerView.scrollToPosition(5);
    }

    private void setLvAdapter() {
        adapter = new MyAdapter(R.layout.item,datas);
        adapter.setLoadMoreView(new CustomLoadMoreView());
        adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override
            public void onLoadMoreRequested() {
                recyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            adapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據(可以直接往適配器添加數據)
                                adapter.addData(datas2);
                                mCurrentCounter = adapter.getData().size();
                                //主動調用加載完成,停止加載
                                adapter.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(MainActivity.this, "加載失敗!", Toast.LENGTH_LONG).show();
                                //同理,加載失敗也要主動調用加載失敗來停止加載(而且該方法會提示加載失敗)
                                adapter.loadMoreFail();

                            }
                        }
                    }

                }, 5000);
            }
        }, recyclerView);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    }

    private void setGvAdapter() {
        adapter2 = new MyAdapter(R.layout.item22,datas);
        // 經測試切換之後adapter1和adapter2的數量保持同步
        Toast.makeText(this, "數量"+adapter2.getData().size(), Toast.LENGTH_SHORT).show();
        adapter2.setLoadMoreView(new CustomLoadMoreView());
        adapter2.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override
            public void onLoadMoreRequested() {
                recyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            adapter2.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據(可以直接往適配器添加數據)
                                adapter2.addData(datas2);
                                mCurrentCounter = adapter2.getData().size();
                                //主動調用加載完成,停止加載
                                adapter2.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(MainActivity.this, "加載失敗!", Toast.LENGTH_LONG).show();
                                //同理,加載失敗也要主動調用加載失敗來停止加載(而且該方法會提示加載失敗)
                                adapter2.loadMoreFail();

                            }
                        }
                    }

                }, 5000);
            }
        }, recyclerView);
        recyclerView.setAdapter(adapter2);
        recyclerView.setLayoutManager(new GridLayoutManager(this,2));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章