這裡我們來說說RecyclerView以及它的自定義分割線(下)
nice,終於有空,這波我們接著上次的繼續說。
上次說的是RecyclerView的Adppter以及它的ViewHolder,這次我們來說說RecyclerView的點選事件以及它的自定義分隔線Decoration。
首先,我們說說點選事件,在RecyclerView的Adapter中的onBindViewHolder中設定它的點選事件,讓我們來看看具體怎麼寫。
我們先建立一個介面 MyRecyclerViewClickListener,它內部有兩個抽象方法,分別是:
interface MyRecyclerViewClickListener {
void click(int position);
boolean longClick(int position);
}
別急,這兩個方法的作用繼續往後看,我們就能清楚。
接著我們來看看Adapter中程式碼應該怎麼寫,程式碼如下:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHodler> {
private List<String> strList;
//這裡定義了剛才我們定義的介面
private MyRecyclerViewClickListener listener;
//添加了一個用於設定MyRecyclerViewClickListener的方法
public void setOnRecyclerViewClickListener(MyRecyclerViewClickListener listener){
this.listener = listener;
}
@Override
public void onBindViewHolder(final MyAdapter.MyViewHodler holder, int position) {
//根據position和初始化MyAdapter傳入的strList 來設定子項中TextView要顯示的資料
holder.view.setText(list.get (position));
//這裡我們對RecyclerView子項中的TextView設定點選事件
holder.tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//呼叫MyRecyclerViewClickListener使具體實現由Activity實現
if (listener != null){
int pos = holder.getLayoutPosition();
listener.onClick(pos);
}else throw new RuntimeException("params exception");
}
});
//同樣我們對長按的事件也響應一下
holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (listener != null){
int pos = holder.getLayoutPosition();
listener.onLongClick(pos);
}else throw new RuntimeException("params exception");
return true;
}
});
}
看上面的程式碼,我們可以知道,我們定義的介面的作用就是能使點選事件在我們想要寫的地方再去實現,就像這個示例裡,我打算在Activity中再去實現我的點選事件,所以我呼叫了這兩個介面中的方法。當然你也可以直接在Adapter中就實現點選事件(劉望舒老師的書中是自己寫了介面的,我個人覺得定義一個介面更好,這樣看起來,或者使用起來更方便),你也可以對RecyclerView中子項的其他控制元件來設定點選事件。這些都看你的實際需求(當然,也可以對子項中的父佈局來設定點選事件,這樣就能包裹整個子項)。
下面,我們看看MainActivity中的程式碼,如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//這裡省略之前的程式碼
adapter.setRecyclerViewClickListener(new MyRecyclerViewClickListener() {
@Override
public void click(int position) {
Toast.makeText(MainActivity.this,"you click "
+position,Toast.LENGTH_SHORT).show();
}
@Override
public boolean longClick(final int position) {
Toast.makeText(MainActivity.this,"you long click"
+position,Toast.LENGTH_SHORT).show();
return true;
}
});
}
}
ok,搞定,這裡我們只是簡單的彈了一個Toast,當然,也可以實現更復雜的點選事件,如長按刪除等,這些就等待朋友們自己實現了。
RecyclerView設定點選事件,其實就是對它的子項設定點選事件(我是這麼理解的)。
說完點選事件,我們來說說分隔線,分隔線還是比較麻煩,需要自己計算,下面,我們就來看看自定義分隔線。
首先,建立一個類繼承RecyclerView.ItemDecoration,如下:
public class MyItemDecoration extends RecyclerView.ItemDecoration {
}
可以看到,繼承以後並沒有必須要重寫的方法。其實ItemDecoration中只有3個方法,如下:
1.void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):此方法是繪製分隔線的方法。
2.void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state):顧名思義,這個方法是繪製完成呼叫的。
3.void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state):
這個方法用於設定item的padding屬性的。
在這裡,主要的是對onDraw方法的重寫,下面我們來看看程式碼:
public class MyItemDecoration extends RecyclerView.ItemDecoration {
//這裡定義的是分隔線的樣式
private static final int[] ATTR = new int[]{android.R.attr.listDivider};
//這裡定義的int值表示RecyclerView子項的排列方式
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
private int mOrientation;
private Context mContext;
private Drawable mDivider;
public MyItemDecoration(Context context,int orientation) {
//獲取樣式的屬性集合,裡面記錄了該分隔線的各種屬性
TypedArray a = context.obtainStyledAttributes(ATTR);
mDivider = a.getDrawable(0);
a.recycle();
mContext =context;
//檢查使用者傳進來的orientation的值是否正確
setOrientation(orientation);
}
private void setOrientation(int orientation) {
if (orientation != VERTICAL && orientation != HORIZONTAL){
throw new RuntimeException("請檢查引數是否正確");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
//根據使用者傳進來的orientation來確定怎麼繪製分隔線
if (mOrientation == VERTICAL){
drawVertical(c,parent);
}else{
drawHorizontal(c,parent);
}
}
//當RecyclerView是水平線排列時呼叫
private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getTop();
int bottom = parent.getBottom();
int count = parent.getChildCount();
for (int i = 0;i < count;i++){
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params=
(RecyclerView.LayoutParams)child.getLayoutParams();
int left = child.getRight()+ params.leftMargin;
int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
//當RecyclerView是豎直排列時呼叫
private void drawVertical(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int count = parent.getChildCount();
Log.v("left",left+"");
for(int i = 0 ; i< count;i++){
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params=
(RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
}
從程式碼中,我們可以知道,樣式是我們定義好的,獲取該樣式的屬性集合,判斷使用者傳進來的引數等等。
最主要的是onDraw方法,根據使用者傳的引數,來繪製分隔線。我們來看看分隔線位置是怎麼計算的。
首先看drawHorizontal,也就是當RecyclerView的子項是水平線排列時,我們來看看程式碼:
private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getPaddingTop()
int bottom = parent.getHeight() - parent.getPaddingBottom();
int count = parent.getChildCount();
for (int i = 0;i < count;i++){
View child = child.getChildAt(i);
RecyclerView.LayoutParams params=
(RecyclerView.LayoutParams)child.getLayoutParams();
int left = parent.getRight()+ params.leftMargin;
int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
當RecyclerView子項水平線排列時,我們的分隔線應該是垂直的豎線。那麼,分隔線的top和bottom是不變的,會變得是分隔線的left和right,首先我們看top和bottom怎麼計算:
int top = parent.getPaddingTop(); 直接獲取RecyclerView中父控制元件的PaddingTop的值
int bottom = parent.getHeight() - parent.getPaddingBottom(); RecyclerView的高度減去父控制元件中的PaddingBottom的值
那麼,決定這兩個值的就是activity_main.xml檔案中 RecyclerView的寬高,以及父控制元件就是LinearLayout的padding屬性了。
再看left和right怎麼算:
int left = child.getRight()+ params.leftMargin; RecyclerView子項的right加上RecyclerView子項的父佈局中的marginRight的值(也就是layout_recycler.xml中RelativeLayout中的Margin值)
int right = left + mDivider.getIntrinsicWidth(); 算出來的left的值加上分隔線的實際寬度
看到這就有點費解了,為什麼left是獲取RecyclerView的right而不是left,這就是分隔線在RecyclerView子項的左邊還是右邊的問題了,如果獲取的是RecyclerView的left則分隔線在子項的左邊,反之則是右邊,算出位置以後後面的程式碼就是繪製分隔線了。
下面,我們看看RecyclerView子項豎直排列怎麼算:
private void drawVertical(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int count = parent.getChildCount();
Log.v("left",left+"");
for(int i = 0 ; i< count;i++){
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params=
(RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
當RecyclerView子項豎直排列時,我們的分隔線應該是水平線。那麼,分隔線的left和right是不變的,會變得是分隔線的top和bottom,首先我們看top和bottom怎麼計算:
int left = parent.getPaddingLeft(); 獲取RecyclerView父控制元件的PaddingLeft
int right = parent.getWidth() - parent.getPaddingRight(); 獲取RecyclerView的寬度減去父控制元件的PaddingRight
那麼,決定這兩個值的就是activity_main.xml檔案中 RecyclerView的寬高,以及父控制元件就是LinearLayout的padding屬性了。
再看top和bottom怎麼算:
int top = child.getBottom() + params.bottomMargin; RecyclerView子項的Bottom值加上定義RecyclerView子項的父佈局的MarginBottom的值(也就是layout_recycler中的RelativeLayout)
int bottom = top + mDivider.getIntrinsicHeight(); 算出來的top的值加上分隔線的實際寬度
同理,child的getBottom或者getTop方法,會改變分隔線的位置算出位置以後後面的程式碼就是繪製分隔線了。
總結一下,這樣定義分隔線的能改變分隔線位置的只有activity_main.xml中LinearLayout的padding會改變分隔線的長度,RecyclerView子項的父佈局(也就是layout_recycler中的RelativeLayout)中的margin屬性會改變分隔線的位置。並且程式碼中,我們用for迴圈遍歷每一個子項。並算出分隔線的位置。
在MainActivity中 我們只要加上一句程式碼就可以了:
recyclerView.addItemDecoration(new MyItemDecoration(this,MyItemDecoration.VERTICAL));
需要注意的是,這句程式碼要在RecyclerView關聯Adapter之前,下面我們來看看效果
如果大家對計算位置有疑問,可以給RecyclerView設定個半透明背景,然後在其他控制元件里加上Padding或者Margin屬性看看。
以上,就是劉望舒老師書中對RecyclerView以及其自定義分隔線的描述,以及我自己的見解。(以後我的博文中會盡量少提到劉望舒老師,以防有人說我給他打廣告,看看我的第一篇博文就知道我為什麼總說他了。)
本人菜鳥,不對之處,望各路大神指教。