Android應(yīng)用開發(fā)中RecyclerView組件使用入門教程
RecyclerView是一種列表容器, 發(fā)布很久了, 才想起來(lái)寫點(diǎn)什么.
RecyclerView相比于ListView, 在回收重用時(shí)更具有靈活性, 也就是低耦合, 并且提供了擴(kuò)展. 加載多個(gè)視圖時(shí), 應(yīng)該多用RecyclerView代替ListView.
那么我們來(lái)看看這東西應(yīng)該怎么用? 比如生成一個(gè)瀑布流的視圖.
首先我們從一個(gè)HelloWorld寫起, 看看如何構(gòu)建一個(gè)RecyclerView.
1. 依賴庫(kù)
Gradle配置, 添加Recycler庫(kù)
compile 'com.android.support:recyclerview-v7:+'
2. 資源文件
資源文件
<android.support.v7.widget.RecyclerView android:id="@+id/test_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
LayoutManager: 管理RecyclerView的結(jié)構(gòu).
Adapter: 處理每個(gè)Item的顯示.
ItemDecoration: 添加每個(gè)Item的裝飾.
ItemAnimator: 負(fù)責(zé)添加\移除\重排序時(shí)的動(dòng)畫效果.
LayoutManager\Adapter是必須, ItemDecoration\ItemAnimator是可選. /** * 初始化RecyclerView * * @param recyclerView 主控件 */ private void initRecyclerView(RecyclerView recyclerView) { recyclerView.setHasFixedSize(true); // 設(shè)置固定大小 initRecyclerLayoutManager(recyclerView); // 初始化布局 initRecyclerAdapter(recyclerView); // 初始化適配器 initItemDecoration(recyclerView); // 初始化裝飾 initItemAnimator(recyclerView); // 初始化動(dòng)畫效果 }
4. LayoutManager
管理RecyclerView的布局結(jié)構(gòu).
private void initRecyclerLayoutManager(RecyclerView recyclerView) { // 錯(cuò)列網(wǎng)格布局 recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL)); }
提供了多種LayoutManager, 瀑布流使用錯(cuò)列網(wǎng)格布局.
5. Adapter
適配器, 處理RecyclerView的Item事務(wù).
private void initRecyclerAdapter(RecyclerView recyclerView) { mAdapter = new MyAdapter(getData()); recyclerView.setAdapter(mAdapter); }
對(duì)于Adapter, 我們需要展開來(lái)說(shuō), 先看看類.
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> { private List<DataModel> mDataModels; private List<Integer> mHeights; MyAdapter(List<DataModel> dataModels) { if (dataModels == null) { throw new IllegalArgumentException("DataModel must not be null"); } mDataModels = dataModels; mHeights = new ArrayList<>(); } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_recycler_view, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { DataModel dataModel = mDataModels.get(position); // 隨機(jī)高度, 模擬瀑布效果. if (mHeights.size() <= position) { mHeights.add((int) (100 + Math.random() * 300)); } ViewGroup.LayoutParams lp = holder.getTvLabel().getLayoutParams(); lp.height = mHeights.get(position); holder.getTvLabel().setLayoutParams(lp); holder.getTvLabel().setText(dataModel.getLabel()); holder.getTvDateTime().setText(new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) .format(dataModel.getDateTime())); } @Override public int getItemCount() { return mDataModels.size(); } public void addData(int position) { DataModel model = new DataModel(); model.setDateTime(getBeforeDay(new Date(), position)); model.setLabel("No. " + (int) (new Random().nextDouble() * 20.0f)); mDataModels.add(position, model); notifyItemInserted(position); } public void removeData(int position) { mDataModels.remove(position); notifyItemRemoved(position); } /** * 獲取日期的前一天 * * @param date 日期 * @param i 偏離 * @return 新的日期 */ private static Date getBeforeDay(Date date, int i) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.DAY_OF_YEAR, i * (-1)); return calendar.getTime(); } }
(1)onCreateViewHolder創(chuàng)建ViewHolder.
(2)onBindViewHolder綁定每一項(xiàng)數(shù)據(jù).
(3)getItemCount返回列表長(zhǎng)度.
(4)RecyclerView強(qiáng)制使用ViewHolder.
public class MyViewHolder extends RecyclerView.ViewHolder { private TextView mTvLabel; // 標(biāo)簽 private TextView mTvDateTime; // 日期 public MyViewHolder(View itemView) { super(itemView); mTvLabel = (TextView) itemView.findViewById(R.id.item_text); mTvDateTime = (TextView) itemView.findViewById(R.id.item_date); } public TextView getTvLabel() { return mTvLabel; } public TextView getTvDateTime() { return mTvDateTime; } }
在onCreateViewHolder方法, 創(chuàng)建類; 在onBindViewHolder方法, 綁定數(shù)據(jù).
DataModel
public class DataModel { private String mLabel; private Date mDateTime; public String getLabel() { return mLabel; } public void setLabel(String label) { mLabel = label; } public Date getDateTime() { return mDateTime; } public void setDateTime(Date dateTime) { mDateTime = dateTime; } }
6. ItemDecoration
項(xiàng)的裝飾, 比如ListView中的分割線, 在本例中, 左右兩條粉線.
private void initItemDecoration(RecyclerView recyclerView) { recyclerView.addItemDecoration(new MyItemDecoration(this)); }
ItemDecoration, 注意parent和child的使用方式.
public class MyItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; private Drawable mDivider; public MyItemDecoration(Context context) { final TypedArray array = context.obtainStyledAttributes(ATTRS); mDivider = array.getDrawable(0); array.recycle(); } @Override public void onDraw(Canvas c, RecyclerView parent, State state) { drawHorizontal(c, parent); drawVertical(c, parent); } // 水平線 public void drawHorizontal(Canvas c, RecyclerView parent) { final int childCount = parent.getChildCount(); // 在每一個(gè)子控件的底部畫線 for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final int left = child.getLeft() + child.getPaddingLeft(); final int right = child.getWidth() + child.getLeft() - child.getPaddingRight(); final int top = child.getBottom() - mDivider.getIntrinsicHeight() - child.getPaddingBottom(); 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(); // 在每一個(gè)子控件的右側(cè)畫線 for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); int right = child.getRight() - child.getPaddingRight(); int left = right - mDivider.getIntrinsicWidth(); final int top = child.getTop() + child.getPaddingTop(); final int bottom = child.getTop() + child.getHeight() - child.getPaddingBottom(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } // Item之間的留白 @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } }
本例重寫了listDivider
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> ... <item name="android:listDivider">@drawable/divider_bg</item> </style> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#ff00ff"/> <size android:height="4dp"/> <size android:width="4dp"/> </shape>
7. ItemAnimator
動(dòng)畫效果比較復(fù)雜, 使用默認(rèn)動(dòng)畫. 如要定制的話, 繼承DefaultItemAnimator; 如設(shè)置null, 則不顯示任何動(dòng)畫.
private void initItemAnimator(RecyclerView recyclerView) { recyclerView.setItemAnimator(new DefaultItemAnimator()); // 默認(rèn)動(dòng)畫 }
8. 最終Activity
public class MainActivity extends AppCompatActivity { private MyAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); // 初始化RecyclerView initRecyclerView((RecyclerView) findViewById(R.id.test_recycler_view)); } /** * 初始化RecyclerView * * @param recyclerView 主控件 */ private void initRecyclerView(RecyclerView recyclerView) { recyclerView.setHasFixedSize(true); // 設(shè)置固定大小 initRecyclerLayoutManager(recyclerView); // 初始化LayoutManager initRecyclerAdapter(recyclerView); // 初始化Adapter initItemDecoration(recyclerView); // 初始化邊界裝飾 initItemAnimator(recyclerView); // 初始化動(dòng)畫效果 } /** * 初始化RecyclerView的LayoutManager * * @param recyclerView 主控件 */ private void initRecyclerLayoutManager(RecyclerView recyclerView) { // 錯(cuò)列網(wǎng)格布局 recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL)); } /** * 初始化RecyclerView的Adapter * * @param recyclerView 主控件 */ private void initRecyclerAdapter(RecyclerView recyclerView) { mAdapter = new MyAdapter(getData()); recyclerView.setAdapter(mAdapter); } /** * 初始化RecyclerView的(ItemDecoration)項(xiàng)目裝飾 * * @param recyclerView 主控件 */ private void initItemDecoration(RecyclerView recyclerView) { recyclerView.addItemDecoration(new MyItemDecoration(this)); } /** * 初始化RecyclerView的(ItemAnimator)項(xiàng)目動(dòng)畫 * * @param recyclerView 主控件 */ private void initItemAnimator(RecyclerView recyclerView) { recyclerView.setItemAnimator(new DefaultItemAnimator()); // 默認(rèn)動(dòng)畫 } /** * 模擬的數(shù)據(jù) * * @return 數(shù)據(jù) */ private ArrayList<DataModel> getData() { int count = 57; ArrayList<DataModel> data = new ArrayList<>(); for (int i = 0; i < count; i++) { DataModel model = new DataModel(); model.setDateTime(getBeforeDay(new Date(), i)); model.setLabel("No. " + i); data.add(model); } return data; } /** * 獲取日期的前一天 * * @param date 日期 * @param i 偏離 * @return 新的日期 */ private static Date getBeforeDay(Date date, int i) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.DAY_OF_YEAR, i * (-1)); return calendar.getTime(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); switch (item.getItemId()) { case R.id.id_action_add: mAdapter.addData(1); break; case R.id.id_action_delete: mAdapter.removeData(1); break; } //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
為了測(cè)試動(dòng)畫, Menu額外添加兩個(gè)按鈕。
9.運(yùn)行ReactNative示例
既然感覺ReactNative開發(fā)靠譜, 那么我們就來(lái)看看ReactNative都能做哪些好玩的東西, 和原生的有哪些區(qū)別?
示例圖
按照文檔安裝一些命令行工具, 再下載Git代碼.
Github: https://github.com/facebook/react-native
內(nèi)容很多, 包含一些依賴庫(kù)和示例(Example), 下載的有點(diǎn)慢, 耐心等待.
下載完成后, 在react-native內(nèi), 執(zhí)行npm install.
Android項(xiàng)目執(zhí)行, 參考ReactAndroid的README.md.
在react-native目錄, 新建local.properties
sdk.dir=/Users/wangchenlong/Installations/android-sdk ndk.dir=/Users/wangchenlong/Installations/android-ndk-r10e
執(zhí)行
cd react-native ./gradlew :ReactAndroid:assembleDebug
再執(zhí)行
./gradlew :ReactAndroid:installArchives
啟動(dòng)服務(wù)
./packager/packager.sh
安裝項(xiàng)目
cd react-native ./gradlew :Examples:UIExplorer:android:app:installDebug
一定要先啟動(dòng)服務(wù), 再安裝項(xiàng)目.
出現(xiàn)transforming 100%, 即導(dǎo)入成功.
在最新版本中, 我的紅米note4無(wú)法運(yùn)行項(xiàng)目.
報(bào)錯(cuò): Upload package to device fails.
原因是編譯的gradle版本太高, 默認(rèn)1.5.0, 實(shí)際1.2.0~1.3.0都可以運(yùn)行.
我的是1.2.3.
真機(jī)調(diào)試, 本人紅米note(Android 4.2)
搖動(dòng)手機(jī), 選擇Dev Settings->Debug sever host & port for device. 設(shè)置IP地址, 觀察本機(jī)的IP, 填入即可. 我當(dāng)前的是
192.168.2.202:8081
注意設(shè)置端口8081, 否則無(wú)法加載. 有些情況可以直接輸入IP即可.
Android5.0以上, 直接設(shè)置端口即可.
adb reverse tcp:8081 tcp:8081
參考Android的真機(jī)調(diào)試文檔.
IOS模擬器, 太窮沒(méi)有iPhone. 直接打開open UIExplorer.xcodeproj項(xiàng)目, 執(zhí)行就可以顯示.
開發(fā)有兩種選擇, 一種是直接基于ReactNative開發(fā), 一種是把ReactNative集成到現(xiàn)有的App中, 對(duì)于第二種, 我們就需要關(guān)注, ReactNative會(huì)增大多少代碼呢?
使用最基本的HelloWorld做測(cè)試, ReactNative也是生成一個(gè)簡(jiǎn)單HelloWorld的JS. 最新生成的HelloWorld的大小是1.4M, 加上ReactNative的是7.6M, 框架大約6.2M左右, 各位可以權(quán)衡一下使用.
ReactNative的UIExplorer已經(jīng)包含了大量示例, 很接近原生, 非常絢麗, 速度也很快. 如Android的ViewPager
OK, 好的開始是成功的一半, 繼續(xù)探索吧! Enjoy it!
相關(guān)文章
Android類加載ClassLoader雙親委托機(jī)制詳解
這篇文章主要為大家介紹了Android類加載ClassLoader雙親委托機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Android 加載assets中的資源文件實(shí)例代碼
這篇文章主要介紹了Android 加載assets中的資源文件實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,需要的朋友可以參考下2017-01-01Android 網(wǎng)絡(luò)圖片查看器與網(wǎng)頁(yè)源碼查看器
本篇文章主要介紹了Android 網(wǎng)絡(luò)圖片查看器與網(wǎng)頁(yè)源碼查看器的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04Android自定義View實(shí)現(xiàn)可以拖拽的GridView
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)可以拖拽的GridView,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06Android中編寫屬性動(dòng)畫PropertyAnimation的進(jìn)階實(shí)例
這篇文章主要介紹了Android中編寫屬性動(dòng)畫PropertyAnimation的進(jìn)階實(shí)例,包括一些縮放和淡入淡出效果的設(shè)計(jì),強(qiáng)大且不算復(fù)雜,需要的朋友可以參考下2016-04-04Android軟件啟動(dòng)動(dòng)畫及動(dòng)畫結(jié)束后跳轉(zhuǎn)的實(shí)現(xiàn)方法
這篇文章主要介紹了Android軟件啟動(dòng)動(dòng)畫及動(dòng)畫結(jié)束后跳轉(zhuǎn)的實(shí)現(xiàn)方法,實(shí)例分析了Android圖片播放及定時(shí)器的相關(guān)使用技巧,非常具有使用價(jià)值,需要的朋友可以參考下2015-10-10Android ViewPager相冊(cè)橫向移動(dòng)的實(shí)現(xiàn)方法
本篇文章小編為大家介紹,Android ViewPager相冊(cè)橫向移動(dòng)的實(shí)現(xiàn)方法。需要的朋友參考下2013-04-04