編輯:關於Android編程
RecyclerView
是Android 5.0
提供的新控件,已經用了很長時間了,但是一直沒有時間去仔細的梳理一下。現在有時間了,決定來整理下。
官方文檔中是這樣介紹的:
A flexible view for providing a limited window into a large data set.
RecyclerView比listview更先進更靈活,對於很多的視圖它就是一個容器,可以有效的重用和滾動。當數據動態變化的時候請使用它。
Adapter
: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
Position
: The position of a data item within an Adapter.
Index
: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position.
Binding
: The process of preparing a child view to display data corresponding to a position within the adapter.
Recycle (view)
: A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.
Scrap (view)
: A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.
Dirty (view)
: A child view that must be rebound by the adapter before being displayed.
RecyclerView
中的位置:RecyclerView
在RecyclerView.Adapter
和RecyclerView.LayoutManager
中引進了一個抽象的額外中間層來保證在布局計算的過程中能批量的監聽到數據變化。這樣介紹了LayoutManager
追蹤adapter
數據變化來計算動畫的時間。因為所有的View
綁定都是在同一時間執行,所以這樣也提高了性能和避免了一些非必要的綁定。
因為這個原因,在RecylcerView
中有兩種position
類型相關的方法:
- layout position
: 在最近一次layout
計算是item
的位置。這是LayoutManager
角度中的位置。
- adapter position
: item
在adapter
中的位置。這是從Adapter
的角度中的位置。
這兩種position
除了在分發adapter.notify*
事件與之後計算布局更新的這段時間之內外都是相同的。
可以通過getLayoutPosition(),findViewHolderForLayoutPosition(int)
方法來獲取最近一次布局計算的LayoutPosition
。這些positions
包括從最近一次布局計算的所有改變。你可以根據這些位置來方便的得到用戶當前從屏幕上所看到的。例如,如果在屏幕上有一個列表,用戶請求的是第五個條目,你可以通過該方法來匹配當前用戶正在看的內容。
另一種AdapterPosition
相關的方法是getAdapterPosition(),findViewHolderForAdapterPosition(int)
,當及時一些數據可能沒有來得及被展現到布局上時便需要獲取最新的adapter
位置可以使用這些相關的方法。例如,如果你想獲取一個條目的ViewHOlder
的click
事件時,你應該使用getAdapterPosition()
。需要知道這些方法在notifyDataSetChange()
方法被調用和新布局還沒有被計算之前是不能使用的。鑒於這個原因,你應該小心的去處理這些方法有可能返回NO_POSITION
或者null
的情況。
RecyclerView.Adapter
: 創建View並將數據集合綁定到View上 ViewHolder
: 持有所有的用於綁定數據或者需要操作的View LayoutManager
: 布局管理器,負責擺放視圖等相關操作 ItemDecoration
: 負責繪制Item
附近的分割線,通過RecyclerView.addItemDecoration()
使用 ItemAnimator
: 為Item
的操作添加動畫效果,如,增刪條目等,通過RecyclerView.setItemAnimator(new DefaultItemAnimator());
使用
下圖能更直觀的了解:
RecyclerView
提供這些內置布局管理器:LinearLayoutManager
: 以垂直或水平滾動列表方式顯示項目。 GridLayoutManager
: 在網格中顯示項目。 StaggeredGridLayoutManager
: 在分散對齊網格中顯示項目。
RecyclerView.ItemDecoration
是一個抽象類,可以通過重寫以下三個方法,來實現Item之間的偏移量或者裝飾效果:public void onDraw(Canvas c, RecyclerView parent)
裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋 public void onDrawOver(Canvas c, RecyclerView parent)
裝飾的繪制在Item條目繪制之後調用,因此裝飾將浮於Item之上 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸並設置偏移量。
ItemAnimator
觸發於以下三種事件:在之前的版本中,當時據集合發生改變時通過調用notifyDataSetChanged()
,來刷新列表,因為這樣做會觸發列表的重繪,所以並不會出現任何動畫效果,因此需要調用一些以notifyItem*()
作為前綴的特殊方法,比如:
public final void notifyItemInserted(int position)
向指定位置插入Item
public final void notifyItemRemoved(int position)
移除指定位置Item
public final void notifyItemChanged(int position)
更新指定位置Item
dependencies {
compile 'com.android.support:recyclerview-v7:23.4.0'
}
示例代碼
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private RecyclerView.Adapter mAdapter;
private String [] mDatas = {"Android","ios","jack","tony","window","mac","1234","hehe","495948", "89757", "66666"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initView();
}
private void findView() {
mRecyclerView = (RecyclerView) findViewById(R.id.rv);
}
private void initView() {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new MyAdapter(mDatas);
mRecyclerView.setAdapter(mAdapter);
}
private class MyAdapter extends RecyclerView.Adapter {
private String[] mData;
public MyAdapter(String[] data) {
mData = data;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item, parent, false);
MyHolder holder = new MyHolder(v);
return holder;
}
@Override
public void onBindViewHolder(MyHolder holder, int position) {
holder.mTitleTv.setText(mData[position]);
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.length;
}
}
static class MyHolder extends RecyclerView.ViewHolder {
public TextView mTitleTv;
public MyHolder(View itemView) {
super(itemView);
mTitleTv = (TextView) itemView;
}
}
}
activity_main的內容如下:
item的內容如下:
之前在使用ListView
的時候,設置點擊事件是非常方便的。
mListView.setOnItemClickListener();
mListView.setOnItemLongClickListener();
但是RecylcerView
確沒有提供類似的方法。那我們只能是自己去處理。處理的方式也有兩種:
通過itemView.onClickListener()
以及onLongClickListener()
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private MyAdapter mAdapter;
private String[] mDatas = {"Android", "ios", "jack", "tony", "window", "mac", "1234", "hehe", "495948", "89757", "66666"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initView();
}
private void findView() {
mRecyclerView = (RecyclerView) findViewById(R.id.rv);
}
private void initView() {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new MyAdapter(mDatas);
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "click " + mDatas[position], Toast.LENGTH_SHORT).show();
}
});
mAdapter.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "long click " + mDatas[position], Toast.LENGTH_SHORT).show();
}
});
mRecyclerView.setAdapter(mAdapter);
}
class MyAdapter extends RecyclerView.Adapter {
private String[] mData;
public MyAdapter(String[] data) {
mData = data;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item, parent, false);
MyHolder holder = new MyHolder(v);
return holder;
}
@Override
public void onBindViewHolder(final MyHolder holder, final int position) {
holder.mTitleTv.setText(mData[position]);
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(holder.itemView, position);
}
});
}
if (mOnItemLongClickListener != null) {
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mOnItemLongClickListener.onItemLongClick(holder.itemView, position);
return true;
}
});
}
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.length;
}
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
this.mOnItemLongClickListener = mOnItemLongClickListener;
}
}
static class MyHolder extends RecyclerView.ViewHolder {
public TextView mTitleTv;
public MyHolder(View itemView) {
super(itemView);
mTitleTv = (TextView) itemView;
}
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public interface OnItemLongClickListener {
void onItemLongClick(View view, int position);
}
}
使用RecyclerView.OnItemTouchListener
雖然沒有提供現成的監聽器,但是提供了一個內部接口OnItemTouchListener
。
先來看看它的介紹:
/**
* An OnItemTouchListener allows the application to intercept touch events in progress at the
* view hierarchy level of the RecyclerView before those touch events are considered for
* RecyclerView's own scrolling behavior.
*
*
This can be useful for applications that wish to implement various forms of gestural * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept * a touch interaction already in progress even if the RecyclerView is already handling that * gesture stream itself for the purposes of scrolling.
* * @see SimpleOnItemTouchListener */ public static interface OnItemTouchListener { ... }
說的很明白了,而且還讓你看SimpleOnItemTouchListener
,猜也能猜出來是一個默認的實現類。
好了直接上代碼:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private MyAdapter mAdapter;
private String[] mDatas = {"Android", "ios", "jack", "tony", "window", "mac", "1234", "hehe", "495948", "89757", "66666"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initView();
}
private void findView() {
mRecyclerView = (RecyclerView) findViewById(R.id.rv);
}
private void initView() {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new MyAdapter(mDatas);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, mDatas[position], Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "Long Click " + mDatas[position], Toast.LENGTH_SHORT).show();
}
}));
}
class RecyclerViewClickListener extends RecyclerView.SimpleOnItemTouchListener {
private GestureDetector mGestureDetector;
private OnItemClickListener mListener;
public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
mListener = listener;
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) {
if (mGestureDetector.onTouchEvent(e)) {
return true;
} else
return super.onInterceptTouchEvent(rv, e);
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
super.onTouchEvent(rv, e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.onRequestDisallowInterceptTouchEvent(disallowIntercept);
}
}
interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
上面的實現稍微有些缺陷,就是如果我手指按住某個條目一直不抬起,他也會執行Long click
事件,這顯然是不合理的,至於怎麼解決,就是可以不用GestureDetector
,自己在DOWN
和UP
事件中去判斷處理。
之前在ListView
中提供了addHeaderView()
和addFooterView()
等方法,但是在RecyclerView
中並沒有提供類似的方法,那我們該如何添加呢? 也很簡單,就是通過Adapter
中去添加,利用不同的itemViewType
,然後根據不同的類型去在onCreateViewHOlder
中創建不同的視圖,通過這種方式來達到headerview
和FooterView
的效果。
上一段簡單的示例代碼:
public class HeaderAdapter extends RecyclerView.Adapter {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
String[] data;
public HeaderAdapter(String[] data) {
this.data = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
//inflate your layout and pass it to view holder
return new VHItem(null);
} else if (viewType == TYPE_HEADER) {
//inflate your layout and pass it to view holder
return new VHHeader(null);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHItem) {
String dataItem = getItem(position);
//cast holder to VHItem and set data
} else if (holder instanceof VHHeader) {
//cast holder to VHHeader and set data for header.
}
}
@Override
public int getItemCount() {
return data.length + 1;
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
private String getItem(int position) {
return data[position - 1];
}
class VHItem extends RecyclerView.ViewHolder {
TextView title;
public VHItem(View itemView) {
super(itemView);
}
}
class VHHeader extends RecyclerView.ViewHolder {
Button button;
public VHHeader(View itemView) {
super(itemView);
}
}
}
上面的代碼對LinearLayoutManger
是沒問題的,但是使用GridLayoutManager
呢? 如果是兩列,那添加的HeaderView
並不是占據上第一行,而是HeaderView
與第二個ItemView
一起占據第一行。那該怎麼處理呢?
那就是使用setSpanSizeLookup()
方法。
比如:
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
在上面的基本設置中,我們的spanCount
為2,每個item
的span size
為1,因此一個header
需要的span size
則為2。在我嘗試著添加header
之前,我想先看看如何設置span size
。其實很簡單。
final GridLayoutManager manager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(manager);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return adapter.isHeader(position) ? manager.getSpanCount() : 1;
}
});
實現下拉刷新也很簡單了,可以使用SwipeRefrshLayout
,SwipeRefrshLayout
是Google
官方提供的組件,可以實現下拉刷新的功能。已包含到support.v4
包中。
主要方法有:
setOnRefreshListener(OnRefreshListener)
:添加下拉刷新監聽器 setRefreshing(boolean)
:顯示或者隱藏刷新進度條 isRefreshing()
:檢查是否處於刷新狀態 setColorSchemeResources()
:設置進度條的顏色主題,最多設置四種。
具體實現就不寫了。
實現方式和ListView
的實現方式類似,就是通過監聽scroll
時間,然後判斷當前顯示的item
。
//RecyclerView滑動監聽
mRecylcerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List newDatas = new ArrayList();
for (int i = 0; i< 5; i++) {
int index = i +1;
newDatas.add("more item" + index);
}
adapter.addMoreItem(newDatas);
}
},1000);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView,dx, dy);
lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();
}
});
然後再通過結合FooterView
以及增加幾種狀態就可以實現自動加載更多了。
SQLite數據庫是android系統內嵌的數據庫,小巧強大,能夠滿足大多數SQL語句的處理工作,而SQLite數據庫僅僅是個文件而已。雖然SQLite的有點很多,但並不
怎樣才能寫出優秀的Android App,是每一個程序員追求的目標。那麼怎麼才能寫出一個優秀的App呢?相信很多初學者也會有這種迷茫。一句話來回答這個問題:細節很重要。今
Activity是Android四大組件之首,本文將介紹Activity的含義、創建、啟動、銷毀、生命周期 等。如需訪問官方原文,您可以點擊這個鏈接:《Activitie
在開發中UI布局是我們都會遇到的問題,隨著UI越來越多,布局的重復性、復雜度也會隨之增長。Android官方給了幾個優化的方法,但是網絡上的資料基本上都是對官方資料的翻譯