文章目錄
一、RecylerView基本使用
1. 添加依賴
- 直接添加依賴:
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01'
- File ->Project Structure ->Dependencies
2. 添加布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerListView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
3. 添加adapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
private List<String> mList;
private Context mContext;
public RecyclerAdapter(Context context, List<String> list) {
this.mContext = context;
this.mList = list;
}
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
/**
* 該方法會在RecyclerView需要展示一個item的時候回調,
* 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
* 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
* */
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(
LayoutInflater.from(mContext)
.inflate(
R.layout.item_recycler,
parent,
false));
return holder;
}
/**
* 該方法是填充綁定item數據的
* */
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.textView.setText(mList.get(position));
}
/**
* 該方法是返回item的數量。
* */
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv);
}
}
}
4. 添加item_recycler.xml
<?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="wrap_content">
<TextView
android:id="@+id/tv"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
5. MainActivity代碼
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerAdapter mRecyclerAdapter;
private List<String> mList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
public void initView() {
//添加測試數據
for (int i = 0; i < 100; i++) {
mList.add(i + " ");
}
//佈局關聯
mRecyclerView = findViewById(R.id.recyclerListView);
//設置佈局方向
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//設置item增加和刪除時的動畫,這裏設置默認動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//設置adapter關聯的list
mRecyclerAdapter = new RecyclerAdapter(this, mList);
//數據更新
mRecyclerAdapter.notifyDataSetChanged();
//關聯adapter
mRecyclerView.setAdapter(mRecyclerAdapter);
}
}
6. 效果
7. 代碼地址
地址:https://github.com/lanjiabin/RecyclerListView
版本選擇RecyclerView(一)
這個版本
二、RecyclerView-setLayoutManager
1. 效果1
setLayoutManager的用法是掌握RecyclerView是一個重要過程。可以設置列表的更多的靈活性。
首先要把item_recycler.xml
裏的TextView中的android:layout_width="match_parent"
改成android:layout_width="wrap_content"
,不然很難看出效果。
先來看看默認效果:
代碼:
//設置佈局方向
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
效果:垂直往下
2. 效果2
代碼:隨後我們修改LinearLayoutManager(this)裏面的參數,第二個參數表示水平佈局,第三個參數表示是否反轉,就會呈現出另一個效果。
//設置佈局方向-水平佈局-不反轉
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false));
效果:可以左右滑動,第一個數據在左邊開始
看看反轉的情況:
//設置佈局方向-水平佈局-反轉
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,true));
效果:第一個數據是從右邊開始的,可以左右滑動。
3. 效果3
表格佈局,第一個參數表示上下文,第二個參數表示表格有多少列。
代碼:
//設置佈局方向-表格佈局
mRecyclerView.setLayoutManager(new GridLayoutManager(this,5));
效果:分爲5列,可以上下滑動
4. 效果4
上面實現了列,那在這裏也可以實現多少行。
代碼:
//設置佈局方向-表格佈局-行
mRecyclerView.setLayoutManager(new GridLayoutManager(this,5,GridLayoutManager.HORIZONTAL,false));
效果:可以發現,有5列,而且數據還是從豎直來排過去的。
三、分割線
1. 佈局中添加分割線
在item_recycler.xml
添加一個佈局作爲分割線。
<?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="wrap_content">
<TextView
android:id="@+id/tv"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
<View
android:background="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="2dp">
</View>
</LinearLayout>
效果:
2. 默認分割線
//添加Android自帶的分割線
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
效果:
3. 自定義Drawable分割線
查看DividerItemDecoration ``extends RecyclerView.ItemDecoration
源碼有setDrawable
這個方法,我們只需要傳入一個Drawable
對象就可以了。
/**
* Sets the {@link Drawable} for this divider.
*
* @param drawable Drawable that should be used as a divider.
*/
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
}
mDivider = drawable;
}
在drawable文件夾中添加一個xml文件:recyclerview_divideritem.xml
代碼:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle">
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
<size android:height="3dp" />
</shape>
然後使用這個文件:
DividerItemDecoration divider=new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
divider.setDrawable(ContextCompat.getDrawable(this,R.drawable.recyclerview_divideritem));
//添加Android自定分割線
mRecyclerView.addItemDecoration(divider);
效果:
4.自定義分割線
這個類放上了前面的幾個方法,做成一個自定義類,方便統一管理和使用。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class RecycleViewDivider extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Drawable mDivider;
private int mDividerHeight = 2;//分割線高度,默認爲1px
private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};//使用系統自帶的listDivider
/**
* 默認分割線:高度爲2px,顏色爲灰色
*
* @param context
* @param orientation 列表方向
*/
public RecycleViewDivider(Context context, int orientation) {
if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
throw new IllegalArgumentException("請輸入正確的參數!");
}
mOrientation = orientation;
final TypedArray a = context.obtainStyledAttributes(ATTRS);//使用TypeArray加載該系統資源
mDivider = a.getDrawable(0);
a.recycle();//緩存
}
/**
* 自定義分割線
*
* @param context
* @param orientation 列表方向
* @param drawableId 分割線圖片
*/
public RecycleViewDivider(Context context, int orientation, int drawableId) {
this(context, orientation);
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDivider.getIntrinsicHeight();
}
/**
* 自定義分割線
*
* @param context
* @param orientation 列表方向
* @param dividerHeight 分割線高度
* @param dividerColor 分割線顏色
*/
public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
this(context, orientation);
mDividerHeight = dividerHeight;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
mPaint.setStyle(Paint.Style.FILL);
}
//獲取分割線尺寸
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//設置偏移的高度是mDivider.getIntrinsicHeight,該高度正是分割線的高度
outRect.set(0, 0, 0, mDividerHeight);
}
//繪製分割線
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
//繪製橫向 item 分割線
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
final int left = parent.getPaddingLeft();//獲取分割線的左邊距,即RecyclerView的padding值
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();//分割線右邊距
final int childSize = parent.getChildCount();
//遍歷所有item view,爲它們的下方繪製分割線
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
//繪製縱向 item 分割線
private void drawVertical(Canvas canvas, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + layoutParams.rightMargin;
final int right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
}
使用方式:
//1.添加默認分割線:高度爲2px,顏色爲灰色
mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL));
//2.添加自定義分割線:可自定義分割線drawable
mRecyclerView.addItemDecoration(new RecycleViewDivider(
this, LinearLayoutManager.HORIZONTAL, R.drawable.recyclerview_divideritem));
//3.添加自定義分割線:可自定義分割線高度和顏色
mRecyclerView.addItemDecoration(new RecycleViewDivider(
this, LinearLayoutManager.HORIZONTAL, 3, getResources().getColor(R.color.colorPrimaryDark)));
四、點擊事件
1. 利用回調機制實現
1.1 添加兩個內部接口:
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public interface OnItemLongClickListener {
void onItemLongClick(View view, int position);
}
1.2 添加變量:
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
public void setmOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public void setmOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
this.mOnItemLongClickListener = mOnItemLongClickListener;
}
1.3 在onBindViewHolder
添加實現回調的方法:
/**
* 該方法是填充綁定item數據的
* */
@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
holder.textView.setText(mList.get(position));
//判斷是否設置了監聽器
if(mOnItemClickListener != null){
//爲ItemView設置監聽器
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition(); // 1
mOnItemClickListener.onItemClick(holder.itemView,position); // 2
}
});
}
if(mOnItemLongClickListener != null){
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = holder.getLayoutPosition();
mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
//返回true 表示消耗了事件 事件不會繼續傳遞
return true;
}
});
}
}
1.4 實現接口:
//item單擊
mRecyclerAdapter.setmOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"點擊了:"+position,Toast.LENGTH_SHORT).show();
}
});
//item長按監聽
mRecyclerAdapter.setmOnItemLongClickListener(new RecyclerAdapter.OnItemLongClickListener() {
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"長點擊了:"+position,Toast.LENGTH_SHORT).show();
}
});
效果:
2. 利用內部接口OnItemTouchListener實現
這其實就是利用View 的事件分發與攔截機制。
2.1 新建一個 RecyclerViewClickListener 類
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.recyclerview.widget.RecyclerView;
public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {
private int mLastDownX,mLastDownY;
//該值記錄了最小滑動距離
private int touchSlop ;
private OnItemClickListener mListener;
//是否是單擊事件
private boolean isSingleTapUp = false;
//是否是長按事件
private boolean isLongPressUp = false;
private boolean isMove = false;
private long mDownTime;
//內部接口,定義點擊方法以及長按方法
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public RecyclerViewClickListener(Context context, OnItemClickListener listener){
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mListener = listener;
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int x = (int) e.getX();
int y = (int) e.getY();
switch (e.getAction()){
/**
* 如果是ACTION_DOWN事件,那麼記錄當前按下的位置,
* 記錄當前的系統時間。
*/
case MotionEvent.ACTION_DOWN:
mLastDownX = x;
mLastDownY = y;
mDownTime = System.currentTimeMillis();
isMove = false;
break;
/**
* 如果是ACTION_MOVE事件,此時根據TouchSlop判斷用戶在按下的時候是否滑動了,
* 如果滑動了,那麼接下來將不處理點擊事件
*/
case MotionEvent.ACTION_MOVE:
if(Math.abs(x - mLastDownX)>touchSlop || Math.abs(y - mLastDownY)>touchSlop){
isMove = true;
}
break;
/**
* 如果是ACTION_UP事件,那麼根據isMove標誌位來判斷是否需要處理點擊事件;
* 根據系統時間的差值來判斷是哪種事件,如果按下事件超過1s,則認爲是長按事件,
* 否則是單擊事件。
*/
case MotionEvent.ACTION_UP:
if(isMove){
break;
}
if(System.currentTimeMillis()-mDownTime > 1000){
isLongPressUp = true;
}else {
isSingleTapUp = true;
}
break;
}
if(isSingleTapUp ){
//根據觸摸座標來獲取childView
View childView = rv.findChildViewUnder(e.getX(),e.getY());
isSingleTapUp = false;
if(childView != null){
//回調mListener#onItemClick方法
mListener.onItemClick(childView,rv.getChildLayoutPosition(childView));
return true;
}
return false;
}
if (isLongPressUp ){
View childView = rv.findChildViewUnder(e.getX(),e.getY());
isLongPressUp = false;
if(childView != null){
mListener.onItemLongClick(childView, rv.getChildLayoutPosition(childView));
return true;
}
return false;
}
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
2.2 MainActivity的調用
//點擊事件,單擊和長擊。缺陷:長按的時候,只有把手放起來,纔會觸發長按事件。
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, new RecyclerViewClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"點擊了:"+position,Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"長點擊了:"+position,Toast.LENGTH_SHORT).show();
}
}));
2.3 提示
方法一和方法二的區別:
首先,方法一我們是直接在MyAdapter數據適配器中,爲itemview設置了內置監聽器,再通過這個監聽器實現我們的回調方法,相當於回調了兩次,同時這個方法與MyAdapter的耦合度比較高,也違反了單一職責原則,當然其簡易性也是突出的優點。
而方法二,我們利用了onTouchListener接口對事件進行了攔截,在攔截中處理我們的點擊事件,實現了與適配器的解耦,但是複雜程度會比方法一大。總地來說,如果RecyclerView需要處理的點擊事件邏輯很簡單,那麼可以使用方法一;如果需要處理比較複雜的點擊事件,比如說,雙擊、長按等點擊事件,則需要使用方法二去實現各種複雜的邏輯。
3. 利用GestureDetector(手勢檢測類)
在實現方法二的RecyclerViewClickListener的時候,在內部對事件的實現了單擊、長按的判斷,但是這個長按事件不是標準的,只有鬆開手指的時候纔會觸發長按事件,這也算是一點瑕疵,同時如果要增加別的事件,比如說雙擊事件,則需要增加相應的邏輯,如果需要判斷的事件種類變多則會給我們的代碼編寫帶來困難,那麼有沒有更加簡便的方法呢?
其實安卓SDK爲我們提供了一個手勢檢測類:GestureDetector來處理各種不同的手勢,那麼我們完全可以利用GestureDetector來對方法二進行改進。
3.1 修改RecyclerViewClickListener 類
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetector mGestureDetector;
private OnItemClickListener mListener;
//內部接口,定義點擊方法以及長按方法
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
mListener = listener;
/**
* 這裏選擇SimpleOnGestureListener實現類,可以根據需要選擇重寫的方法
* */
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
//單擊事件
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null) {
mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
//長按事件
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
//把事件交給GestureDetector處理
if (mGestureDetector.onTouchEvent(e)) {
return true;
} else
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
3.2 修改調用方法
//利用GestureDetector手勢檢測類來實現回調
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"單擊了:"+position,Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"長擊了:"+position,Toast.LENGTH_SHORT).show();
}
}));
3.3 github代碼
五、修改數據和背景
先認識官方提供的方法
//該方法用於當增加一個數據的時候,position表示新增數據顯示的位置
final void notifyItemInserted(int position)
//該方法用於刪除一個數據的時候,position表示數據刪除的位置
final void notifyItemRemoved(int position)
//該方法表示所在position對應的item位置不會改變,但是該item內容發生變化
final void notifyItemChanged(int position)
//該方法一般用於:適配器之前裝載的數據大部分已經過時了,需要重新更新數據
//調用該方法的時候,recyclerView會重新計算子item及所有子item重新佈局
//出於效率考慮,官方建議用更加精確的方法(比如上面三個方法)來取代這個方法
final void notifyDataSetChanged()
1. 在RecyclerAdapter
添加修改方法
//記錄用戶選擇了RecyclerListView哪個item
//賦值-1是爲了防止默認選中position=0的item
private int mPosition = -1;
//獲取當前點擊item的position
public void getCurrentPosition(int position) {
this.mPosition = position;
}
//移除數據
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
//新增數據
public void addData(int position) {
mList.add(position, "Add One" + position);
notifyItemInserted(position);
}
//更改某個位置的數據
public void changeData(int position) {
mList.set(position, "Item " + position + " has changed");
notifyItemChanged(position);
}
/**
* 該方法是填充綁定item數據的
*/
@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
holder.textView.setText(mList.get(position));
//修改選中item的顏色
if (mPosition == position) {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
} else {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
}
}
2. 添加數據按鈕和修改按鈕
在activity_main.xml中加入
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/addData"
android:text="addData"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
<Button
android:layout_weight="1"
android:id="@+id/changeData"
android:text="changeData"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
</LinearLayout>
3. MainActivity.java添加代碼
private Button addDataButton, changeData;
//記錄用戶選擇了RecyclerListView哪個item
private int mPosition;
public void initView() {
addDataButton = findViewById(R.id.addData);
changeData = findViewById(R.id.changeData);
addDataButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//添加數據到選擇item的上方
mRecyclerAdapter.addData(mPosition);
}
});
changeData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//修改選中item的數據
mRecyclerAdapter.changeData(mPosition);
}
});
}
//利用GestureDetector手勢檢測類來實現回調
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
//顏色改變了,更新Adapter
mRecyclerAdapter.notifyDataSetChanged();
//記錄用戶選擇了哪個item
mPosition = position;
//傳遞點擊了哪個item,以此用來更新選中item的顏色
mRecyclerAdapter.getCurrentPosition(position);
Toast.makeText(MainActivity.this, "單擊了:" + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "長擊了:" + position, Toast.LENGTH_SHORT).show();
//刪除長按的item
mRecyclerAdapter.removeData(position);
}
}));
4. github代碼
5. 效果
六、添加header和footer
1.1 添加header和footer的佈局文件
footer_recylerview.xml和header_recylerview.xml是一樣的,不過id不同而已。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/headerLinerLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/headerTextView"
android:text="@string/header"
android:layout_width="match_parent"
android:layout_height="100dp">
</TextView>
</LinearLayout>
1.2 添加屬性
/**
* 記錄用戶選擇了RecyclerListView哪個item
* 賦值-1是爲了防止默認選中position=0的item
*/
private int mPosition = -1;
public static final int TYPE_HEADER = 0; //說明是帶有Header的
public static final int TYPE_FOOTER = 1; //說明是帶有Footer的
public static final int TYPE_NORMAL = 2; //說明是不帶有header和footer的
/**
* mHeaderView 頭部
* mFooterView 尾部
*/
private View mHeaderView;
private View mFooterView;
1.3 在構造方法中添加mHeaderView和mFooterView對象
public RecyclerAdapter(Context context, List<String> list, RecyclerView recyclerView) {
this.mContext = context;
this.mList = list;
this.mHeaderView = LayoutInflater.from(mContext).inflate(R.layout.header_recylerview, recyclerView, false);
this.mFooterView = LayoutInflater.from(mContext).inflate(R.layout.footer_recylerview, recyclerView, false);
}
1.4 根據view類型判斷加載佈局
//獲取item的view的類型
@Override
public int getItemViewType(int position) {
if (mHeaderView == null && mFooterView == null) {
return TYPE_NORMAL;
}
if (position == 0 && mHeaderView != null) {
return TYPE_HEADER;
}
if (position == getItemCount() - 1 && mFooterView != null) {
return TYPE_FOOTER;
}
return TYPE_NORMAL;
}
1.5 返回對象
/**
* 該方法會在RecyclerView需要展示一個item的時候回調,
* 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
* 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
*/
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (mHeaderView != null && viewType == TYPE_HEADER) {
return new MyViewHolder(mHeaderView);
}
if (mFooterView != null && viewType == TYPE_FOOTER) {
return new MyViewHolder(mFooterView);
}
MyViewHolder holder = new MyViewHolder(
LayoutInflater.from(mContext)
.inflate(
R.layout.item_recyclerview,
parent,
false));
return holder;
}
1.6 包括header和footer在內的每個item的設置
/**
* 該方法是填充綁定item數據的
*/
@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
if (getItemViewType(position) == TYPE_NORMAL) {
if (holder instanceof MyViewHolder) {
holder.textView.setText(mList.get(position - 1));
//修改選中item的顏色
if (mPosition == position) {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
} else {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
}
return;
}
return;
} else if (getItemViewType(position) == TYPE_HEADER) {
holder.headerTextView.setText("----我是頭部header----");
return;
} else if (getItemViewType(position) == TYPE_FOOTER) {
holder.footerTextView.setText("++++我是尾部footer+++++");
return;
}
}
1.7 item數目的變化
由於添加了header和footer,所以item會發生變化。
/**
* 該方法是返回item的數量。
*/
@Override
public int getItemCount() {
if (mHeaderView == null && mFooterView == null) {
return mList.size();
} else if (mHeaderView == null && mFooterView != null) {
return mList.size() + 1;
} else if (mHeaderView != null && mFooterView == null) {
return mList.size() + 1;
} else {
return mList.size() + 2;
}
1.8 ViewHolder中關聯佈局
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView, headerTextView, footerTextView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
/**
* 如果是headerView或者是footerView,直接返回
* */
if (itemView == mHeaderView) {
headerTextView = mHeaderView.findViewById(R.id.headerTextView);
return;
}
if (itemView == mFooterView) {
footerTextView = mFooterView.findViewById(R.id.footerTextView);
return;
}
textView = itemView.findViewById(R.id.tv);
}
}
1.9 完整的adapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
private List<String> mList;
private Context mContext;
/**
* 記錄用戶選擇了RecyclerListView哪個item
* 賦值-1是爲了防止默認選中position=0的item
*/
private int mPosition = -1;
public static final int TYPE_HEADER = 0; //說明是帶有Header的
public static final int TYPE_FOOTER = 1; //說明是帶有Footer的
public static final int TYPE_NORMAL = 2; //說明是不帶有header和footer的
/**
* mHeaderView 頭部
* mFooterView 尾部
*/
private View mHeaderView;
private View mFooterView;
public RecyclerAdapter(Context context, List<String> list, RecyclerView recyclerView) {
this.mContext = context;
this.mList = list;
this.mHeaderView = LayoutInflater.from(mContext).inflate(R.layout.header_recylerview, recyclerView, false);
this.mFooterView = LayoutInflater.from(mContext).inflate(R.layout.footer_recylerview, recyclerView, false);
}
//獲取當前點擊item的position
public void getCurrentPosition(int position) {
this.mPosition = position;
}
//移除數據
public void removeData(int position) {
/**
* 這裏要重點講解一下;
* 添加了header和footer之後;
* header的位置就是recyclerView是在position爲0位置,footer是在position爲recyclerView.size()-1的位置;
* 那數據list真正佔用的位置就是recyclerView的 1 --> recyclerView.size()-2 位置
* 當點擊recyclerView的footer的前一個位置的時候(recyclerView.size()-2)
* 得到的recyclerView的position爲recyclerView.size()+1,而list的position爲list.size()
* 位置差了一個1,所以在傳遞最後一個數據點擊刪除的時候,會數組越界
* 舉例:position指的是下標
* list的長度爲10,那數據的position就是 0 ~ 9;
* 加入header和footer後,recyclerView的position=0 的位置就是header;
* position=11 的位置就是footer;
* list的 position=0 的數據放在recyclerView的 position=1 的位置;
* list的 position=9 的數據放在recyclerView的 position=10 的位置;
* 長按recyclerView的 position=10的位置的時候,傳遞的方法remove(position)中的position=10;
* 傳遞的是recyclerView的position,就成了 mList.remove(10);
* mList.remove(10),明顯數組越界了,因爲mList最大的position爲9;
* 所以在刪除最後一個數據的時候,要特別處理,就是下面的方法。
* 重點是notifyItemRemoved(position);方法
* 刪除的position - 1處的list數據
* 而更新的是recyclerView的position的位置
* 因爲他們錯位了 1
* */
mList.remove(position - 1);
notifyItemRemoved(position);
}
//新增數據
public void addData(int position) {
mList.add(position, "Add One" + position);
notifyItemInserted(position);
}
//更改某個位置的數據
public void changeData(int position) {
mList.set(position, "Item " + position + " has changed");
notifyItemChanged(position);
}
//獲取item的view的類型
@Override
public int getItemViewType(int position) {
if (mHeaderView == null && mFooterView == null) {
return TYPE_NORMAL;
}
if (position == 0 && mHeaderView != null) {
return TYPE_HEADER;
}
if (position == getItemCount() - 1 && mFooterView != null) {
return TYPE_FOOTER;
}
return TYPE_NORMAL;
}
/**
* 該方法會在RecyclerView需要展示一個item的時候回調,
* 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
* 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
*/
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (mHeaderView != null && viewType == TYPE_HEADER) {
return new MyViewHolder(mHeaderView);
}
if (mFooterView != null && viewType == TYPE_FOOTER) {
return new MyViewHolder(mFooterView);
}
MyViewHolder holder = new MyViewHolder(
LayoutInflater.from(mContext)
.inflate(
R.layout.item_recyclerview,
parent,
false));
return holder;
}
/**
* 該方法是填充綁定item數據的
*/
@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
if (getItemViewType(position) == TYPE_NORMAL) {
if (holder instanceof MyViewHolder) {
holder.textView.setText(mList.get(position - 1));
//修改選中item的顏色
if (mPosition == position) {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
} else {
holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
}
return;
}
return;
} else if (getItemViewType(position) == TYPE_HEADER) {
holder.headerTextView.setText("----我是頭部header----");
return;
} else if (getItemViewType(position) == TYPE_FOOTER) {
holder.footerTextView.setText("++++我是尾部footer+++++");
return;
}
}
/**
* 該方法是返回item的數量。
*/
@Override
public int getItemCount() {
if (mHeaderView == null && mFooterView == null) {
return mList.size();
} else if (mHeaderView == null && mFooterView != null) {
return mList.size() + 1;
} else if (mHeaderView != null && mFooterView == null) {
return mList.size() + 1;
} else {
return mList.size() + 2;
}
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView, headerTextView, footerTextView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
/**
* 如果是headerView或者是footerView,直接返回
* */
if (itemView == mHeaderView) {
headerTextView = mHeaderView.findViewById(R.id.headerTextView);
return;
}
if (itemView == mFooterView) {
footerTextView = mFooterView.findViewById(R.id.footerTextView);
return;
}
textView = itemView.findViewById(R.id.tv);
}
}
}
1.10 MainActivity中使用
這裏有一些細節更新,就是數組越界問題,具體看GitHub代碼。
//設置adapter關聯的list
mRecyclerAdapter = new RecyclerAdapter(this, mList,mRecyclerView);
完整的MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerAdapter mRecyclerAdapter;
private List<String> mList = new ArrayList<>();
private Button addDataButton, changeData;
/**
* 記錄用戶選擇了RecyclerListView哪個item
* 賦值-1是爲了防止默認選中position=0的item
*/
private int mPosition=-1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initRecyclerView();
}
public void initView() {
addDataButton = findViewById(R.id.addData);
changeData = findViewById(R.id.changeData);
mRecyclerView = findViewById(R.id.recyclerListView);
//添加數據到選擇item的上方
addDataButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
* 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
* */
if (mPosition>=0 && mPosition<mList.size()){
mRecyclerAdapter.addData(mPosition);
mRecyclerAdapter.notifyDataSetChanged();
}
}
});
//修改選中item的數據
changeData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
* 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
* */
if (mPosition>=0 && mPosition<mList.size()){
mRecyclerAdapter.changeData(mPosition);
mRecyclerAdapter.notifyDataSetChanged();
}
}
});
}
public void initRecyclerView() {
//添加測試數據
for (int i = 0; i < 20; i++) {
mList.add(i + "-----");
}
//設置佈局方向-默認佈局
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//設置item增加和刪除時的動畫,這裏設置默認動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//1.添加默認分割線:高度爲2px,顏色爲灰色
mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL));
//設置adapter關聯的list
mRecyclerAdapter = new RecyclerAdapter(this, mList,mRecyclerView);
//利用GestureDetector手勢檢測類來實現回調
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
//顏色改變了,更新Adapter
mRecyclerAdapter.notifyDataSetChanged();
//記錄用戶選擇了哪個item
mPosition = position-1;
//傳遞點擊了哪個item,以此用來更新選中item的顏色
mRecyclerAdapter.getCurrentPosition(position);
Toast.makeText(MainActivity.this, "單擊了:" + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
/**
* 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
* 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
* */
if (position>0 && position<=mList.size()){
//長按刪除的item
mRecyclerAdapter.removeData(position);
}
Toast.makeText(MainActivity.this, "長擊了:" + (position), Toast.LENGTH_SHORT).show();
}
}));
//關聯adapter
mRecyclerView.setAdapter(mRecyclerAdapter);
}
}