列表控件:RecyclerView

一、添加依賴

app.gradle裏添加依賴:

 compile 'com.android.support:recyclerview-v7:26.+'

如果使用的是androidx:

  implementation 'androidx.recyclerview:recyclerview:1.1.0'

二、基本使用方法

1、activity的xml佈局

就是寫一個RecyclerView的控件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:scrollbars="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

2、列表的item佈局

隨意寫了一下,此處純展示功能,所以就寫了個TextView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="#d4d2d2"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="match_parent"
        android:background="#FFFFFF"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:gravity="center"
        android:textSize="22sp"
        android:text="item"/>

</LinearLayout>

3、Adapter

public class InfoRecyclerViewAdapter extends RecyclerView.Adapter<InfoRecyclerViewAdapter.ViewHolder> {

    private ArrayList<String> mData;
     private Context mContext;//記錄一個Context,如果以後需要彈出個提示框這種的,也好用
  
    /**
     * 事件回調監聽
     */
    private InfoRecyclerViewAdapter.OnItemClickListener onItemClickListener;

    public InfoRecyclerViewAdapter(Context context,ArrayList<String> data) {
        this.mContext = context;
        this.mData = data;      
    }
   public InfoRecyclerViewAdapter(Context context) {
      this.mContext = context;
   }

     /**
     * 新增數據:得到此次選擇的數據後,更新數據
     */
    public void initData(ArrayList<String> data,int pageIndex) {
       if (mData == null) {
            mData = new ArrayList<>();
        }
       if(pageIndex == 0){//現在增加的是第一頁數據,則清除之前的數據
          mData.clear();
       }
        if (data != null) {
            int start = mData.size();  
            mData.addAll(data);         
            int count = mData.size() - start;
            notifyItemRangeChanged(start, count);
        }
    }
   
   /**
   * 提供給外部獲取數據源的方法
   */
     public List<String> getDatas() {
        List<String> list = new ArrayList<>(mData);
        return list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        // 實例化展示的view
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_vertical, parent, false);
        // 實例化viewholder
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        // 綁定數據
        holder.tvItem.setText(mData.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemClick(holder.itemView, pos);
                }
            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemLongClick(holder.itemView, pos);
                }
                //表示此事件已經消費,不會觸發單擊事件
                return true;
            }
        });
    }

    @Override
    public int getItemViewType(int position) {
        if(position == getItemCount() - 1){
            return 1;
        }else {
            return 0;
        }
    }
    
    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    /**
     * 往列表第一行新增一個item
     */
    public void addNewItem() {
        if(mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(0, "new Item");
        notifyItemInserted(0);
    }

    /**
     * 刪除item
     */
    public void deleteItem(int position) {
        if(mData == null || mData.isEmpty()) {
            return;
        }
        if(position >= 0 && position < mData.size()){
           mData.remove(position);
           notifyItemRemoved(position);
        }
    }

    /**
     * 設置回調監聽
     * @param listener
     */
    public void setOnItemClickListener(InfoRecyclerViewAdapter.OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
        void onItemLongClick(View view, int position);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.tv_item)
        TextView tvItem;

        ViewHolder(View view) {
            super( view);
            ButterKnife.bind(this, view);
        }
    }
}

activity往列表中新增數據:

ArrayList<String> mDataList= new ArrayList<>();//activity裏的數據源,獲取到數據後就傳到adapter裏更新列表

獲取到數據後就傳到adapter裏更新列表:

infoRecyclerViewAdapter.initData(mDataList);
 // 由於Adapter內部是直接在首個Item位置做增加操作,增加完畢後列表移動到首個Item位置
infoLayoutManager.scrollToPosition(0);

刪除某個位置的item:

infoRecyclerViewAdapter.deleteItem(2);//比如我要刪除位置爲第三的item
// 刪除完畢後列表移動到Item位置
 infoLayoutManager.scrollToPosition(2);

4、activity

//activity裏的數據源,獲取到數據後就傳到adapter裏更新列表
ArrayList<String> mDataList= new ArrayList<>();

網格列表(像GridView):

 //豎直滑動,每行只顯示四個
 GridLayoutManager infoLayoutManager = new GridLayoutManager( this,4);
//如果在初始化adapter的時候就已經獲取到了數據源可以在初始化adapter的時候就傳入數據源
  InfoRecyclerViewAdapter infoRecyclerViewAdapter = new InfoRecyclerViewAdapter(this,mDataList);

  //如果像是異步網絡請求數據後再刷新列表的話:
   InfoRecyclerViewAdapter infoRecyclerViewAdapter = new InfoRecyclerViewAdapter(this);
   //獲取到數據後就傳到adapter裏更新列表
   infoRecyclerViewAdapter.initData(mDataList,0); 
   // 設置佈局管理器
  infoRecyclerView.setLayoutManager(infoLayoutManager);
   // 設置adapter
   infoRecyclerView.setAdapter(infoRecyclerViewAdapter);
    //設置間隔樣式
    infoRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));

垂直列表(像ListView):數據更新方式同上

infoLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
// 設置佈局管理器
infoRecyclerView.setLayoutManager(infoLayoutManager);
// 設置adapter
infoRecyclerViewAdapter = new InfoRecyclerViewAdapter(mDataList);
//item的點擊事件
infoRecyclerViewAdapter.setOnItemClickListener(new InfoRecyclerViewAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(ActVerticalRecycler.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(ActVerticalRecycler.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
            }
 });
infoRecyclerView.setAdapter(infoRecyclerViewAdapter);
//設置間隔樣式
// infoRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));
infoRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
// 設置Item添加和移除的動畫
 infoRecyclerView.setItemAnimator(new DefaultItemAnimator());

水平列表:

 // 設置佈局管理器
        infoLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        infoRecyclerView.setLayoutManager(infoLayoutManager);
        // 設置adapter
        infoRecyclerViewAdapter = new InfoRecyclerViewAdapter( mDataList );
        infoRecyclerView.setAdapter(infoRecyclerViewAdapter);
        //設置間隔樣式
//        infoRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.HORIZONTAL));
        infoRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));

設置列表item之間的間隔

  //設置列表item之間的間隔
        recycleView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                outRect.left = 2;
                outRect.right = 2;
                outRect.top = 2;
                outRect.bottom = 2;
//                super.getItemOffsets(outRect, view, parent, state);
            }
        });

三、 長按拖動

/**
 * 長按拖拽
 */
public class ItemDragHelperCallBack extends ItemTouchHelper.Callback {

    private OnItemDragListener onItemDragListener;

    public ItemDragHelperCallBack(OnItemDragListener onItemDragListener) {
        this.onItemDragListener = onItemDragListener;
    }

    /**
     * 返回可以滑動的方向
     *
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        int dragFlags;
        if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) {
            //網格佈局管理器允許上下左右拖動
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        } else {
            //其他佈局管理器允許上下拖動
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        }
        return makeMovementFlags(dragFlags, 0);
    }

    /**
     * 拖拽到新位置時候的回調方法
     *
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //不同Type之間不允許移動
        if (viewHolder.getItemViewType() != target.getItemViewType()) {
            return false;
        }
        if (onItemDragListener != null) {
            onItemDragListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        }
        return true;
    }

    /**
     * 當用戶左右滑動的時候執行的方法
     *
     * @param viewHolder
     * @param direction
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

    }

    /**
     * 重寫拖拽不可用
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return false;
    }

    public void setOnItemDragListeber(OnItemDragListener onItemDragListener) {
        this.onItemDragListener = onItemDragListener;
    }

    public interface OnItemDragListener {
        void onItemMove(int startPos,int endPos);
    }
}

activity中使用:

mItemTouchHelper = new ItemTouchHelper(new ItemDragHelperCallBack(new ItemDragHelperCallBack.OnItemDragListener() {

            @Override
            public void onItemMove(int startPos, int endPos) {
                //交換變換位置的集合數據並刷新
                Collections.swap(mAdapter.getDatas(), startPos, endPos);
                mAdapter.notifyItemMoved(startPos, endPos);
            }
        }));
//關聯RecyclerView
mItemTouchHelper.attachToRecyclerView(recyclerView);

四、滑動衝突

1、ScrollView嵌套RecycleView

xml佈局:

<ScrollView
        android:layout_below="@+id/title_bar"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:paddingTop="10dp"
            android:paddingBottom="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_marginTop="20dp"
                    android:scrollbars="none"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
        </LinearLayout>
    </ScrollView>

代碼設置:

 GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext,4){
            @Override
            public boolean canScrollVertically() {
                //解決ScrollView裏存在多個RecyclerView時滑動卡頓的問題
                return false;
            }
        };
        recyclerView.setLayoutManager(gridLayoutManager);
        //解決數據加載不完的問題
        recyclerView.setNestedScrollingEnabled(false);
        recyclerView.setHasFixedSize(true);
        //解決數據加載完成後, 沒有停留在頂部的問題
        recyclerView.setFocusable(false);

Android在開發中的使用技巧之解決ScrollView嵌套RecyclerView出現的系列問題 - 簡書

五、recyclerview的item位序

1、Recyclerview.getLayoutPosition()問題

使用Recyclerview 時,如果要添加item的點擊監聽等功能,可以在Recyclerview.Adapter的onBindViewHolder中設置:

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
    holder.tv.setHeight(150*(1+position%4));
    holder.tv.setWidth(150*(1+position%4));
    holder.tv.setText(data.get(position));
    if(mOnItemClickListener!=null){
        holder.tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int pos=holder.getLayoutPosition();
                mOnItemClickListener.onItemClick(v,pos);
            }
        });
        holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                int pos=holder.getLayoutPosition();
                mOnItemClickListener.onItemLongClick(v,pos);
                return false;
            }
        });
    }
}

注意這裏使用了ViewHolder的getLayoutPosition方法,此方法返回的pos值與onBindViewHolder方法傳入的position值有可能不同。
根據SDK中的解釋,在Recyclerview 進行添加、移除item等操作時,position位置可能會變化,而所有的adapter的刷新並不總是及時的,只有這個方法返回的纔是當前item經過一些變換後所處的真正位置。
getLayoutPosition:返回佈局中最新的計算位置,和用戶所見到的位置一致,當做用戶輸入(例如點擊事件)的時候考慮使用

getAdapterPosition:返回數據在Adapter中的位置(也許位置的變化還未來得及刷新到佈局中),當使用Adapter的時候(例如調用Adapter的notify相關方法時)考慮使用

六、分割線

1、ItemDecoration樣式drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <size android:height="4dp"/>

</shape>
 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name= "android:listDivider">@drawable/bg_divider </item >       
    </style>

2、ItemDecoration

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    private Drawable mDivider;

    public DividerGridItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

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

    /**
     * 繪製水平分割線
     */
    public void drawHorizontal(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();//item總個數
        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.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin
                    + mDivider.getIntrinsicWidth();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
    /**
     * 繪製垂直分割線
     */
    public void drawVertical(Canvas c, RecyclerView parent) {
        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.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

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

    /**
     * 是否是最後一列
     */
    private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
                                int childCount) {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            if ((pos + 1) % spanCount == 0) // 如果是最後一列,則不需要繪製右邊
            {
                return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                if ((pos + 1) % spanCount == 0) // 如果是最後一列,則不需要繪製右邊
                {
                    return true;
                }
            } else {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount) // 如果是最後一列,則不需要繪製右邊
                    return true;
            }
        }
        return false;
    }

    /**
     * 是否是最後一行
     * @param parent
     * @param pos 當前item的位序
     * @param spanCount 列數
     * @param childCount item總個數
     * @return
     */
    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
                              int childCount) {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            childCount = childCount - childCount % spanCount;
            // 如果是最後一行,則不需要繪製底部
            if (pos >= childCount){
                return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            // StaggeredGridLayoutManager 且縱向滾動
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                childCount = childCount - childCount % spanCount;
                // 如果是最後一行,則不需要繪製底部
                if (pos >= childCount){
                    return true;
                }
            } else {
                // StaggeredGridLayoutManager 且橫向滾動
                // 如果是最後一行,則不需要繪製底部
                if ((pos + 1) % spanCount == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition,
                               RecyclerView parent) {
        int spanCount = getSpanCount(parent);//列數
        int childCount = parent.getAdapter().getItemCount();//item總個數
        if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最後一行,則不需要繪製底部
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最後一列,則不需要繪製右邊
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章