RecyclerView是一種列表容器, 發布很久了, 才想起來寫點什麼.
RecyclerView相比於ListView, 在回收重用時更具有靈活性, 也就是低耦合, 並且提供了擴充. 載入多個視圖時, 應該多用RecyclerView代替ListView.
那麼我們來看看這東西應該怎麼用? 比如產生一個瀑布流的視圖.
首先我們從一個HelloWorld寫起, 看看如何構建一個RecyclerView.
1. 依賴庫
Gradle配置, 添加Recycler庫
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"/>
3. 代碼
LayoutManager: 管理RecyclerView的結構.
Adapter: 處理每個Item的顯示.
ItemDecoration: 添加每個Item的裝飾.
ItemAnimator: 負責添加\移除\重排序時的動畫效果.
LayoutManager\Adapter是必須, ItemDecoration\ItemAnimator是可選. /** * 初始化RecyclerView * * @param recyclerView 主控制項 */ private void initRecyclerView(RecyclerView recyclerView) { recyclerView.setHasFixedSize(true); // 設定固定大小 initRecyclerLayoutManager(recyclerView); // 初始化布局 initRecyclerAdapter(recyclerView); // 初始化適配器 initItemDecoration(recyclerView); // 初始化裝飾 initItemAnimator(recyclerView); // 初始化動畫效果 }
4. LayoutManager
管理RecyclerView的布局結構.
private void initRecyclerLayoutManager(RecyclerView recyclerView) { // 錯列網格布局 recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL)); }
提供了多種LayoutManager, 瀑布流使用錯列網格布局.
5. Adapter
適配器, 處理RecyclerView的Item事務.
private void initRecyclerAdapter(RecyclerView recyclerView) { mAdapter = new MyAdapter(getData()); recyclerView.setAdapter(mAdapter); }
對於Adapter, 我們需要展開來說, 先看看類.
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); // 隨機高度, 類比瀑布效果. 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建立ViewHolder.
(2)onBindViewHolder綁定每一項資料.
(3)getItemCount返回列表長度.
(4)RecyclerView強制使用ViewHolder.
public class MyViewHolder extends RecyclerView.ViewHolder { private TextView mTvLabel; // 標籤 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方法, 建立類; 在onBindViewHolder方法, 綁定資料.
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
項的裝飾, 比如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(); // 在每一個子控制項的底部畫線 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(); // 在每一個子控制項的右側畫線 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
動畫效果比較複雜, 使用預設動畫. 如要定製的話, 繼承DefaultItemAnimator; 如設定null, 則不顯示任何動畫.
private void initItemAnimator(RecyclerView recyclerView) { recyclerView.setItemAnimator(new DefaultItemAnimator()); // 預設動畫 }
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); // 設定固定大小 initRecyclerLayoutManager(recyclerView); // 初始化LayoutManager initRecyclerAdapter(recyclerView); // 初始化Adapter initItemDecoration(recyclerView); // 初始化邊界裝飾 initItemAnimator(recyclerView); // 初始化動畫效果 } /** * 初始化RecyclerView的LayoutManager * * @param recyclerView 主控制項 */ private void initRecyclerLayoutManager(RecyclerView recyclerView) { // 錯列網格布局 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)項目裝飾 * * @param recyclerView 主控制項 */ private void initItemDecoration(RecyclerView recyclerView) { recyclerView.addItemDecoration(new MyItemDecoration(this)); } /** * 初始化RecyclerView的(ItemAnimator)項目動畫 * * @param recyclerView 主控制項 */ private void initItemAnimator(RecyclerView recyclerView) { recyclerView.setItemAnimator(new DefaultItemAnimator()); // 預設動畫 } /** * 類比的資料 * * @return 資料 */ 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); }}
為了測試動畫, Menu額外添加兩個按鈕。
9.運行ReactNative樣本
既然感覺ReactNative開發靠譜, 那麼我們就來看看ReactNative都能做哪些好玩的東西, 和原生的有哪些區別?
樣本圖
按照文檔安裝一些命令列工具, 再下載Git代碼.
Github: https://github.com/facebook/react-native
內容很多, 包含一些依賴庫和樣本(Example), 下載的有點慢, 耐心等待.
下載完成後, 在react-native內, 執行npm install.
Android項目執行, 參考ReactAndroid的README.md.
在react-native目錄, 建立local.properties
sdk.dir=/Users/wangchenlong/Installations/android-sdkndk.dir=/Users/wangchenlong/Installations/android-ndk-r10e
執行
cd react-native./gradlew :ReactAndroid:assembleDebug
再執行
./gradlew :ReactAndroid:installArchives
啟動服務
安裝項目
cd react-native./gradlew :Examples:UIExplorer:android:app:installDebug
一定要先啟動服務, 再安裝項目.
出現transforming 100%, 即匯入成功.
在最新版本中, 我的紅米note4無法運行項目.
報錯: Upload package to device fails.
原因是編譯的gradle版本太高, 預設1.5.0, 實際1.2.0~1.3.0都可以運行.
我的是1.2.3.
真機調試, 本人紅米note(Android 4.2)
搖動手機, 選擇Dev Settings->Debug sever host & port for device. 設定IP地址, 觀察原生IP, 填入即可. 我當前的是
192.168.2.202:8081
注意設定連接埠8081, 否則無法載入. 有些情況可以直接輸入IP即可.
Android5.0以上, 直接設定連接埠即可.
adb reverse tcp:8081 tcp:8081
參考Android的真機調試文檔.
IOS模擬器, 太窮沒有iPhone. 直接開啟open UIExplorer.xcodeproj項目, 執行就可以顯示.
開發有兩種選擇, 一種是直接基於ReactNative開發, 一種是把ReactNative整合到現有的App中, 對於第二種, 我們就需要關注, ReactNative會增大多少代碼呢?
使用最基本的HelloWorld做測試, ReactNative也是產生一個簡單HelloWorld的JS. 最新產生的HelloWorld的大小是1.4M, 加上ReactNative的是7.6M, 架構大約6.2M左右, 各位可以權衡一下使用.
ReactNative的UIExplorer已經包含了大量樣本, 很接近原生, 非常絢麗, 速度也很快. 如Android的ViewPager
OK, 好的開始是成功的一半, 繼續探索吧! Enjoy it!