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"/>
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
启动服务
./packager/packager.sh
安装项目
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!
新闻热点
疑难解答
图片精选