RecyclerView更全解析之 - 基本使用和分割線解析

1.概述


昨天跟自己群里的人嘮嗑的時候發(fā)現還有人在用Eclipse,我相信可能還是有很多人在用ListView,這里介紹一個已經出來的n年了的控件RecyclerView,實現ListView,GridView,瀑布流的效果。還可以輕松的實現一些復雜的功能,如QQ的拖動排序,側滑刪除等等。

視頻講解:http://pan.baidu.com/s/1jHW1X10

相關文章:
  
  RecyclerView更全解析之 - 基本使用和分割線解析
  
  RecyclerView更全解析之 - 打造通用的萬能Adapter
  
  RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部
  
  RecyclerView更全解析之 - 打造通用的下拉刷新上拉加載
  
  RecyclerView更全解析之 - 仿支付寶側滑刪除和拖動排序

這里寫圖片描述

2.概述


最終我們肯定要用到項目中,因為我覺得所有寫的東西都必須封裝好下次開發(fā)可以直接用到項目中。但是首先得要熟悉該控件才行??梢赃x擇去google官方了解RecyclerView,但要翻墻。

據官方的介紹,該控件用于在有限的窗口中展示大量數據集,其實這樣功能的控件我們并不陌生,例如:ListView、GridView。

那么有了ListView、GridView為什么還需要RecyclerView這樣的控件呢?整體上看RecyclerView架構,提供了一種插拔式的體驗,高度的解耦,異常的靈活,通過設置它提供的不同LayoutManager,ItemDecoration , ItemAnimator實現令人瞠目的效果。

1.你想要控制其顯示的方式,請通過布局管理器LayoutManager,ListView–>GridView–>瀑布流 只需要一行代碼;
2.你想要控制Item間的間隔(可繪制),請通過ItemDecoration(這個比較蛋疼) ;
3.你想要控制Item增刪的動畫,請通過ItemAnimator;
4.你想要控制點擊、長按事件,請自己寫(擦,這點尼瑪)。

3.基本使用


首先我們需要添加RecyclerView的依賴包,在build.gradle中添加依賴:

compile 'com.android.support:recyclerview-v7:24.0.0'

和ListView一樣通過設置Adapter()給他綁定數據源,但在這之前必須設置setLayoutManager()這個方法是用來設置顯示效果的(這樣我們就可以通過設置布局管理顯示不同的效果:ListView、GridView、瀑布流等等)。

    // 設置recyclerView的布局管理  
    // LinearLayoutManager -> ListView風格
    // GridLayoutManager -> GridView風格
    // StaggeredGridLayoutManager -> 瀑布流風格
    LinearLayoutManager linearLayoutManager = new 
          LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(linearLayoutManager);

3.1 Adapter編寫

接下來我們先去后臺服務器請求數據,然后我們來編寫Adapter,我們直接使用Okhttp獲取服務器數據,在這里就不多說了,我們主要看Adapter怎么寫。在ListView和GridView中我們可以不用ViewHolder,反正沒人打我,只是數據特別多可能會崩潰而已;但是在RecyclerView中就不一樣了它強制我們使用ViewHolder:

/**
 * Created by Darren on 2016/12/27.
 * Email: 240336124@qq.com
 * Description: 熱吧訂閱列表的Adapter
 */
public class CategoryListAdapter extends RecyclerView.Adapter<CategoryListAdapter.ViewHolder> {

    private List<ChannelListResult.DataBean.CategoriesBean.CategoryListBean> mList;
    private Context mContext;
    private LayoutInflater mInflater;

    public CategoryListAdapter(Context context, List<ChannelListResult.DataBean.CategoriesBean.CategoryListBean> list) {
        this.mContext = context;
        this.mList = list;
        this.mInflater = LayoutInflater.from(mContext);
    }

    /**
     * 創(chuàng)建條目ViewHolder
     *
     * @param parent   RecyclerView
     * @param viewType view的類型可以用來顯示多列表布局等等
     * @return
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 創(chuàng)建條目
        View itemView = mInflater.inflate(R.layout.channel_list_item, parent, false);
        // 創(chuàng)建ViewHolder
        ViewHolder viewHolder = new ViewHolder(itemView);
        return viewHolder;
    }

    /**
     * 綁定ViewHolder設置數據
     *
     * @param holder
     * @param position 當前位置
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 設置綁定數據
        ChannelListResult.DataBean.CategoriesBean.CategoryListBean item = mList.get(position);
        holder.nameTv.setText(item.getName());
        holder.channelTopicTv.setText(item.getIntro());
        String str = item.getSubscribe_count() + " 訂閱 | " +
                "總帖數 <font color='#FF678D'>" + item.getTotal_updates() + "</font>";
        holder.channelUpdateInfo.setText(Html.fromHtml(str));
        // 是否是最新
        if (item.isIs_recommend()) {
            holder.recommendLabel.setVisibility(View.VISIBLE);
        } else {
            holder.recommendLabel.setVisibility(View.GONE);
        }
        // 加載圖片
        Glide.with(mContext).load(item.getIcon_url()).centerCrop().into(holder.channelIconIv);
    }

    /**
     * 總共有多少條數據
     */
    @Override
    public int getItemCount() {
        return mList.size();
    }

    /**
     * RecyclerView的Adapter需要一個ViewHolder必須要extends RecyclerView.ViewHolder
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView nameTv;
        public TextView channelTopicTv;
        public TextView channelUpdateInfo;
        public View recommendLabel;
        public ImageView channelIconIv;

        public ViewHolder(View itemView) {
            super(itemView);
            // 在創(chuàng)建的時候利用傳遞過來的View去findViewById
            nameTv = (TextView) itemView.findViewById(R.id.channel_text);
            channelTopicTv = (TextView) itemView.findViewById(R.id.channel_topic);
            channelUpdateInfo = (TextView) itemView.findViewById(R.id.channel_update_info);
            recommendLabel = itemView.findViewById(R.id.recommend_label);
            channelIconIv = (ImageView) itemView.findViewById(R.id.channel_icon);
        }
    }
}

3.2 分隔線定制

對于分隔線這個也比較蛋疼,你會發(fā)現RecyclerView并沒有支持divider這樣的屬性。那么怎么辦,你可以在創(chuàng)建item布局的時候直接寫在布局中,當然了這種方式不夠優(yōu)雅,我們早就說了可以自由的去定制它。
  既然比較麻煩那么我們可以去github上面下載一個:DividerItemDecoration來參考一下。我這里就直接來寫一個效果,大家也可以找找別人的博客看看。
  分割線我們利用RecyclerView的addItemDecoration(ItemDecoration fromHtml) 新建一個類來看看到底是什么:

/**
 * Created by Darren on 2016/12/27.
 * Email: 240336124@qq.com
 * Description: RecyclerView 分割線定制
 */
public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        
    }
}

有兩個方法getItemOffsets()這里我一般指定偏移量就可以了,就是分割線占多少高度,或者說是畫在什么位置,你總的給我留出位置來;onDraw()我們可以直接去繪制,繪制什么都可以因為有Canvas ,但一般都是繪制Drawable,這里不多說具體看視頻吧。

/**
 * Created by Darren on 2016/12/27.
 * Email: 240336124@qq.com
 * Description: RecyclerView 分割線定制
 */
public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
    private Paint mPaint;

    public CategoryItemDecoration(int color) {
        // 直接繪制顏色  只是用來測試
        mPaint = new Paint();
        mPaint.setColor(color);
        mPaint.setAntiAlias(true);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();
        // 獲取需要繪制的區(qū)域
        Rect rect = new Rect();
        rect.left = parent.getPaddingLeft();
        rect.right = parent.getWidth() - parent.getPaddingRight();
        for (int i = 0; i < childCount; i++) {
            View childView = parent.getChildAt(i);
            rect.top = childView.getBottom();
            rect.bottom = rect.top + 20;
            // 直接利用Canvas去繪制一個矩形 在留出來的地方
            c.drawRect(rect, mPaint);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // 在每個子View的下面留出20px來畫分割線
        outRect.bottom += 20;
    }
}

看一下效果

這里寫圖片描述

這只是用來測試一下,并不是我們所需要的效果,只是說一下這兩個方法都可以干什么就是你想怎么弄分割線就怎么弄。我們一般會使用Drawable去畫,所以我們必須調整成我們最終的效果,代碼其實基本一致。

/**
 * Created by Darren on 2016/12/27.
 * Email: 240336124@qq.com
 * Description: RecyclerView 分割線定制
 */
public class CategoryItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable mDivider;

    public CategoryItemDecoration(Drawable divider) {
        // 利用Drawable繪制分割線
        mDivider = divider;
    }

    @Override
    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();
        // 計算需要繪制的區(qū)域
        Rect rect = new Rect();
        rect.left = parent.getPaddingLeft();
        rect.right = parent.getWidth() - parent.getPaddingRight();
        for (int i = 0; i < childCount; i++) {
            View childView = parent.getChildAt(i);
            rect.top = childView.getBottom();
            rect.bottom = rect.top + mDivider.getIntrinsicHeight();
            // 直接利用Canvas去繪制
            mDivider.draw(canvas);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // 在每個子View的下面留出來畫分割線
        outRect.bottom += mDivider.getIntrinsicHeight();
    }
}

接下來我們就可以在drawable,下面新建一個xxx.xml的分割線文件了

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:height="0.5dp"   />
    <solid android:color="@color/list_item_divider" />
</shape>
這里寫圖片描述

基本沒什么效果因為分割線比較小0.5dp,仔細看還是看得出來的,最后聲明一下因為整個項目涉及到一鍵更換皮膚,那么恭喜恭喜這樣做并沒什么卵用,我們還是得直接寫在item布局中,而不是用代碼設置分割線。

3.3 RecyclerView分割線源碼解析
  
  估計很大一部分的哥們在開發(fā)的時候都是使用的第三方的分割線,基本上都類似,一般都是利用系統(tǒng)自帶的一個屬性 android.R.attrs.listDriver我們直接獲取這個屬性的Drawable去繪制,那么這里我們分析一下源碼去了解一些RecyclerView到底是怎樣添加分割線的。
  一萬一千多行代碼我們就只挑關鍵的方法:measureChild(),onDraw() , soeasy()

    // 只挑關鍵代碼 一萬一千多行太多了
    /**
    * Measure a child view using standard measurement policy, taking the padding
    * of the parent RecyclerView and any added item decorations into account.
    *
    * <p>If the RecyclerView can be scrolled in either dimension the caller may
    * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
    *
    * @param child Child view to measure
    * @param widthUsed Width in pixels currently consumed by other views, if relevant
    * @param heightUsed Height in pixels currently consumed by other views, if relevant
    */
    public void measureChild(View child, int widthUsed, int heightUsed) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        // 關鍵看這個方法 --> getItemDecorInsetsForChild
        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
        widthUsed += insets.left + insets.right;
        heightUsed += insets.top + insets.bottom;
        // 考慮分割線返回的Rect
        final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
            getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
            canScrollHorizontally());
        final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
            getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
            canScrollVertically());
        if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
            child.measure(widthSpec, heightSpec);
        }
    }

    /**
    *  獲取分割線裝飾的Rect
    **/
    Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }

        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            // getItemOffsets()還是比較熟悉,獲取分割線返回的占用位置
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            // 開始累加占用位置
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        // 返回分割線的Rect
        return insets;
    }

    // onDraw()方法異常簡單,自己體會一下吧
    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            // 回調出去直接在getItemOffsets留出分割線位置的基礎上直接繪制
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

3.4 長按和點擊事件
  
  這個是第二坑,第一坑就是上面的,后面還會有第三坑,RecyclerView并沒有方法可以setOnItemClickListener()。我們只能在Adapter里面去寫接口了,但是想想我們會有很多Adapter的每個都單獨的去寫會不會很麻煩。這個不用擔心后面我們會寫萬能的Adapter,后面也會去自定義RecyclerView,目前只能這么弄了,修改修改Adapter:

/**
 * Created by Darren on 2016/12/27.
 * Email: 240336124@qq.com
 * Description: 熱吧訂閱列表的Adapter
 */
public class CategoryListAdapter extends RecyclerView.Adapter<CategoryListAdapter.ViewHolder> {

    // 省略之前的代碼 ......

    /**
     * 綁定ViewHolder設置數據
     *
     * @param holder
     * @param position 當前位置
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        // 省略之前的代碼 ......

        // 設置點擊和長按事件
        if (mItemClickListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mItemClickListener.onItemClick(position);
                }
            });
        }
        if (mLongClickListener != null) {
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mLongClickListener.onLongClick(position);
                }
            });
        }
    }
    
    // 省略之前的代碼 ......


    /***************
     * 給條目設置點擊和長按事件
     *********************/
    public OnItemClickListener mItemClickListener;
    public OnLongClickListener mLongClickListener;

    public void setOnItemClickListener(OnItemClickListener itemClickListener) {
        this.mItemClickListener = itemClickListener;
    }

    public void setOnLongClickListener(OnLongClickListener longClickListener) {
        this.mLongClickListener = longClickListener;
    }

    public interface OnItemClickListener {
        public void onItemClick(int position);
    }

    public interface OnLongClickListener {
        public boolean onLongClick(int position);
    }
}

估計有些不想接觸新事物的哥們會覺得,哪里好用。他們都說好我覺得也好用著用著就好了因為公司的人都在用我不用也沒辦法。下一期我們還是解析RecyclerView可能會要更好用一些.

附視頻地址:http://pan.baidu.com/s/1jHW1X10

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容