利用RecyclerView实现探探的翻牌子功能
2017-11-22 11:08
393 查看
探探的翻牌子
仿照后的
需要基础
RecyclerView的基本使用可以看鸿神的:Android RecyclerView 使用完全解析 体验艺术般的控件
自定义LayoutManager
可以看:打造属于你的LayoutManager
ItemTouchHelper的使用以及回调的实现
可以看:ItemTouchHelper帮助实现拖拽效果
以上是通过本文来实现翻牌子功能的所需要的基础,也都比较简单,相信读者通过上面3篇文章,就能学会基本用法。
本文目的
复习下RecyclerView的基本用法。巩固RecyclerView中的LayoutManager的自定义。
巩固RecyclerView中的ItemToucher的用法以及ItemTouchCallBack的自定义。
源码地址
https://github.com/qq908323236/TanTanCard整体思路
屏幕上任意时刻都是只有一定数量的卡片(探探的是4张,本文也中4张),滑动后销毁掉一个,最底下又会创建一个,具有这样的特性,那么我们就可以利用RecyclerView的回收复用的机制,去实现这样的功能。然后我们需要自定义LayoutManager来实现卡片层叠式布局,最后通过ItemTouchHelper及其它的回调来实现滑动的删除,以及滑动中的卡片的动画。具体实现
添加依赖
在app的build.gradle中的dependencies{}添加implementation ‘com.android.support:recyclerview-v7:26.1.0’
implementation ‘com.android.support:cardview-v7:26.1.0’
implementation ‘com.github.bumptech.glide:glide:4.3.1’
annotationProcessor ‘com.github.bumptech.glide:compiler:4.3.1’
我升级成AS3.0了,可以用implementation来依赖,据说要稍微快一点,如果不是3.0的就把implementation改成compile吧。
recyclerview-v7肯定是必须的
glide用来显示图片
cardview是卡片式布局
实体类User
每个Item需要展示的数据,这里为了方便,图片直接用本地的,所以实体类中直接记录图片的资源IDpublic class User { private int photoResId; //图片的资源ID private String name; //名字 private String sign; //个性签名 public User(int photoResId, String name, String sign) { this.photoResId = photoResId; this.name = name; this.sign = sign; } //省略get,set }
卡片的基本参数配置CardConfig
/** * 初始化一些配置信息、固定数据 */ public class CardConfig { //屏幕上最多同时显示几个Item public static int MAX_SHOW_COUNT; //每一级Scale相差0.05f,translationY相差15dp,translationZ相差0.5dp左右 public static float SCALE_GAP; public static int TRANS_Y_GAP; public static int TRANS_Z_GAP; public static void initConfig(Context context) { MAX_SHOW_COUNT = 4; SCALE_GAP = 0.05f; //这里是把dp转换成px TRANS_Y_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, context.getResources().getDisplayMetrics()); TRANS_Z_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.getResources().getDisplayMetrics()); } }
示意图
TRANS_Z_GAP图中未标出是因为不好画,Z方向是垂直于屏幕的,每张卡片Z的不同就会有层次感,最底下的Z坐标最小,最上面的Z坐标最大。
基本布局
MainActivity的布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.fu.tantancard.MainActivity"> <TextView android:id="@+id/tv_love_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="80dp" android:layout_marginLeft="60dp" android:text="喜欢:0" android:textColor="@android:color/black" android:textSize="20sp" /> <TextView android:id="@+id/tv_del_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginBottom="80dp" android:layout_marginRight="60dp" android:text="删除:0" android:textColor="@android:color/black" android:textSize="20sp" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </RelativeLayout>
Item卡片的布局,这里最外层布局用的是CardView,因为可以直接设置圆角,好看点。
item_card.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="380dp" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_marginTop="10dp" app:cardCornerRadius="5dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_photo" android:layout_width="match_parent" android:layout_height="300dp" android:background="#4cc6ff" /> <ImageView android:id="@+id/iv_love" android:layout_width="70dp" android:layout_height="70dp" android:alpha="0" android:src="@mipmap/icon_love" /> <ImageView android:id="@+id/iv_del" android:layout_width="70dp" android:layout_height="70dp" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:alpha="0" android:src="@mipmap/icon_del" /> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/iv_photo" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:text="Rachel" android:textColor="#000000" android:textSize="18sp" android:textStyle="bold" /> <TextView android:id="@+id/tv_sign" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_name" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:text="其他" android:textSize="10sp" /> </RelativeLayout> </android.support.v7.widget.CardView>
RecyclerView的基本操作
在MainActivity的onCreate中初始化UI和数据,以及配置好RecyclerView,SwipeCardLayoutManager()是我们自定义的LayoutManger,是重点,下文会讲,先写着。initUI(); mData = initData(); //初始化卡片的基本配置参数 CardConfig.initConfig(this); mRecyclerView.setLayoutManager(new SwipeCardLayoutManager()); mAdapter = new CardAdapter(); mRecyclerView.setAdapter(mAdapter);
初始化UI和数据
private void initUI() { loveCount = 0; delCount = 0; tv_love_count = findViewById(R.id.tv_love_count); tv_love_count.setText("喜欢:" + loveCount); tv_del_count = findViewById(R.id.tv_del_count); tv_del_count.setText("删除:" + delCount); this.mRecyclerView = findViewById(R.id.recycler_view); } private List<User> initData() { List<User> datas = new ArrayList<>(); datas.add(new User(R.mipmap.pic1, "名字1", "其他1")); datas.add(new User(R.mipmap.pic2, "名字2", "其他2")); datas.add(new User(R.mipmap.pic3, "名字3", "其他3")); datas.add(new User(R.mipmap.pic4, "名字4", "其他4")); datas.add(new User(R.mipmap.pic5, "名字5", "其他5")); datas.add(new User(R.mipmap.pic6, "名字6", "其他6")); datas.add(new User(R.mipmap.pic7, "名字7", "其他7")); datas.add(new User(R.mipmap.pic8, "名字8", "其他8")); return datas; }
为了方便Adapter直接写在了MainActivity中
class CardAdapter extends RecyclerView.Adapter<CardAdapter.CardViewHolder> { @Override public CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_card, parent, false); CardViewHolder holder = new CardViewHolder(view); return holder; } @Override public void onBindViewHolder(CardViewHolder holder, int position) { holder.tv_name.setText(mData.get(position).getName()); holder.tv_sign.setText(mData.get(position).getSign()); //用Glide来加载图片 Glide.with(getApplicationContext()) .load(mData.get(position).getPhotoResId()) .apply(new RequestOptions().transform(new CenterCrop())) .into(holder.iv_photo); } @Override public int getItemCount() { return mData.size(); } //右滑的时候调用 public void addLoveCount() { loveCount++; tv_love_count.setText("喜欢:" + loveCount); } //左滑的时候调用 public void addDelCount(){ delCount++; tv_del_count.setText("删除:" + delCount); } class CardViewHolder extends RecyclerView.ViewHolder { ImageView iv_photo; TextView tv_name; TextView tv_sign; ImageView iv_love; ImageView iv_del; public CardViewHolder(View itemView) { super(itemView); iv_photo = itemView.findViewById(R.id.iv_photo); tv_name = itemView.findViewById(R.id.tv_name); tv_sign = itemView.findViewById(R.id.tv_sign); iv_love = itemView.findViewById(R.id.iv_love); iv_del = itemView.findViewById(R.id.iv_del); } } }
自定义LayoutManager实现卡片层叠式布局
新建一个类SwipeCardLayoutManager并继承RecyclerView.LayoutManager,然后重写两个方法:generateDefaultLayoutParams()
onLayoutChildren()
重写generateDefaultLayoutParams()一般这需如下写法,
@Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); }
重点是在onLayoutChildren(),注释也比较详细。大体思路就是这里只Layout出一定数量的卡片(这里是4个),然后Layout的时候需要从最底层开始,因为后绘制的会把先绘制的覆盖住,这里有个小陷阱,如果后绘制的Z坐标比先绘制的Z坐标小,那先绘制的在视觉上覆盖了后绘制的,但是,在后面需要加的触摸事件上,是后绘制的接收了事件,就会差生滑动卡片的时候是画的下面的,而不是最上面的。所以这里,从下往上绘制,而且后绘制的Z坐标要比先绘制的Z坐标大。
通过recycler.getViewForPosition(position)可以根据position从缓存池中获取View,循环绘制,position = 3 , 2, 1, 0这样的顺序绘制,然后滑掉一个从上层的卡片,数组中把postion=0的移除,然后调用adapter.notifyDataSetChanged(),相应的onLayoutChildren()也会调用,然后又重新绘制,就跟数据结构中的队列一样,先进先出,不过还需要注意边界的问题,比如只剩2张卡片了,所以要算好下标position。
后面就是根据层级来调整卡片的大小,以及Y坐标和Z坐标的值,具体配合着注释代码,很容易理解。
/** * 在这里面给子view布局,也就是item * * @param recycler * @param state */ @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { //在布局之前,将所有的子View先Detach掉,放入到Scrap缓存中 detachAndScrapAttachedViews(recycler); //拿到总item数量 int itemCount = getItemCount(); if (itemCount < 1) {//没有item当然就没必要布局了 return; } int bottomPosition; //用来记录最底层view的postion if (itemCount < CardConfig.MAX_SHOW_COUNT) { //如果不足最大数量(4个) //那么最底层就是最后一条数据对应的position bottomPosition = itemCount - 1; } else { //否则最底层就是第MAX_SHOW_COUNT(4)条数据对应的position bottomPosition = CardConfig.MAX_SHOW_COUNT - 1; } /** * 这里开始布局且绘制子view * 注意:这里要先从最底层开始绘制,因为后绘制的才能覆盖先绘制的, * 滑动的时候是滑最上面一层的,也就是后绘制的 * position也是层数 */ for (int position = bottomPosition; position >= 0; position--) { //根据position找recycler要itemview View view = recycler.getViewForPosition(position); //将子View添加至RecyclerView中 addView(view); //测量子view并且把Margin也作为子控件的一部分 measureChildWithMargins(view, 0, 0); //宽度空隙 getWidth()得到Recycler控件的宽度,getDecoratedMeasuredWidth(view)拿到子view的宽度 int widthSpace = getWidth() - getDecoratedMeasuredWidth(view); //高度空隙 int heightSpace = getHeight() - getDecoratedMeasuredHeight(view); //给子view布局,这里居中了 layoutDecoratedWithMargins(view, widthSpace / 2, 200, widthSpace / 2 + getDecoratedMeasuredWidth(view), getDecoratedMeasuredHeight(view) + 200); /** * 下面要调整每一层itemview的的大小及Y轴和Z轴的偏移 * 最上面一层(第0层)的Scale为1,translationY为0 * 依次往下,每层比上面一层: * (1)Scale相差0.05f * (2)translationY相差7dp * (3)translationZ相差1dp * * 注意:最后一层,除了水平方向的大小其他都与上一层一样,所以要特殊判断 */ if (position > 0) { //大于0就是不是最上面那层 //依次往下,每层都要水平方向缩小 view.setScaleX(1 - CardConfig.SCALE_GAP * position); if (position < CardConfig.MAX_SHOW_COUNT - 1) { //如果,不是最后一层,就都要调整 view.setScaleY(1 - CardConfig.SCALE_GAP * position); //垂直方向缩小 view.setTranslationY(CardConfig.TRANS_Y_GAP * position); //向下平移 view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - position)); //Z轴方向的平移 } else { //否则,就是最后一层,与上一层保持一致 view.setScaleY(1 - CardConfig.SCALE_GAP * (position - 1)); //垂直方向缩小 view.setTranslationY(CardConfig.TRANS_Y_GAP * (position - 1)); //向下平移 view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - (position - 1))); //Z轴方向的平移 } } else { //否则,是第0层(最上面那层),只需调整Z轴高度 view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1)); //Z轴方向的平移 } } }
效果图
当然,现在是不能滑动的,需要滑动,就要ItemTouchHelper出场了。
自定义ItemTouchHelper.Callback
新建一个类CardItemTouchCallBack并继承ItemTouchHelper.Callback,然后需要重写很多方法,代码有点多,但是不要怕,配合注释,一步一步的来,在这个里面会完成滑动事件,以及卡片的动画。public class CardItemTouchCallBack extends ItemTouchHelper.Callback { private static final String TAG = "CardItemTouchCallBack"; private RecyclerView mRecyclerView; private MainActivity.CardAdapter mAdapter; private List mDatas; public CardItemTouchCallBack(RecyclerView recyclerView, MainActivity.CardAdapter adapter, List datas) { this.mRecyclerView = recyclerView; this.mAdapter = adapter; this.mDatas = datas; } /** * 是否开启长按拖拽 * true,开启 * false,不开启长按退拽 * * @return */ @Override public boolean isLongPressDragEnabled() { return false; } /** * 是否开启滑动 * true,开启 * false,不开启长按退拽 * * @return */ @Override public boolean isItemViewSwipeEnabled() { return true; } /** * ItemTouchHelper支持设置事件方向,并且必须重写当前getMovementFlags来指定支持的方向 * dragFlags 表示拖拽的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN * swipeFlags 表示滑动的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN * 最后要通过makeMovementFlags(dragFlag,swipe)创建方向的Flag */ @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { /** * 由于我们不需要长按拖拽,所以直接传入0即可,传入0代表不监听 */ int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN; return makeMovementFlags(0, swipeFlags); } /** * 长按item就可以拖动,然后拖动到其他item的时候触发onMove * 这里我们不需要 * * @param recyclerView * @param viewHolder 拖动的viewholder * @param target 目标位置的viewholder * @return */ @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } /** * 把item滑走(飞出屏幕)的时候调用 * * @param viewHolder 滑动的viewholder * @param direction 滑动的方向 */ @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { //这里来判断画出的方向,左边是4,右边是8,然后可以做一些数据操作 switch (direction) { case 4: Log.d(TAG, "onSwiped: 左边滑出"); mAdapter.addDelCount(); break; case 8: Log.d(TAG, "onSwiped: 右边滑出"); mAdapter.addLoveCount(); break; } //移除这条数据 Object remove = mDatas.remove(viewHolder.getLayoutPosition()); /** 这个位置可以用来加载数据,当滑到还剩4个或者多少个时可以在后面加载数据,添加到mDatas中*/ //这里就为了方便,直接循环了,把移除的元素再添加到末尾 mDatas.add(mDatas.size(), remove); //刷新 mAdapter.notifyDataSetChanged(); //复位 viewHolder.itemView.setRotation(0); if (viewHolder instanceof MainActivity.CardAdapter.CardViewHolder) { MainActivity.CardAdapter.CardViewHolder holder = (MainActivity.CardAdapter.CardViewHolder) viewHolder; holder.iv_love.setAlpha(0f); holder.iv_del.setAlpha(0f); } } /** * 只要拖动、滑动了item,就会触发这个方法,而且是动的过程中会一直触发 * 所以动画效果就是在这个方法中来实现的 * * @param c * @param recyclerView * @param viewHolder * @param dX * @param dY * @param actionState * @param isCurrentlyActive */ @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); double swipeValue = Math.sqrt(dX * dX + dY * dY); //滑动离中心的距离 double fraction = swipeValue / (mRecyclerView.getWidth() * 0.5f); //边界修正 最大为1 if (fraction > 1) { fraction = 1; } /** * 调整每个子view的缩放、位移之类的 */ int childCount = recyclerView.getChildCount(); //拿到子view的数量 isUpOrDown(mRecyclerView.getChildAt(childCount - 1)); for (int i = 0; i < childCount; i++) { /** 拿到子view 注意这里,先绘制的i=0,所以最下面一层view的i=0,最上面的i=3*/ View childView = recyclerView.getChildAt(i); int level = childCount - i - 1; //转换一下,level代表层数,最上面是第0层 if (level > 0) { //下面层,每一层的水平方向都要增大 childView.setScaleX((float) (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP)); if (level < CardConfig.MAX_SHOW_COUNT - 1) { //1 2层 childView.setScaleY((float) (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP)); childView.setTranslationY((float) (CardConfig.TRANS_Y_GAP * level - fraction * CardConfig.TRANS_Y_GAP)); childView.setTranslationZ((float) (CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - level) + fraction * CardConfig.TRANS_Z_GAP)); } else { //最下面一层,3层,这层不用变,所以这里不用写 } } else { //第0层 //拿到水平方向的偏移比率 float xFraction = dX / (mRecyclerView.getWidth() * 0.5f); //边界修正,有正有负,因为旋转有两个方向 if (xFraction > 1) { xFraction = 1; } else if (xFraction < -1) { xFraction = -1; } //第一层左右滑动的时候稍微有点旋转 childView.setRotation(xFraction * 15); //这里最多旋转15度 if (viewHolder instanceof MainActivity.CardAdapter.CardViewHolder) { MainActivity.CardAdapter.CardViewHolder holder = (MainActivity.CardAdapter.CardViewHolder) viewHolder; if (dX > 0) { //右滑,显示爱心 holder.iv_love.setAlpha(xFraction); } else if (dX < 0) { //左滑,显示叉,注意这里xFraction为负数,所以要取反 holder.iv_del.setAlpha(-xFraction); } else { holder.iv_love.setAlpha(0f); holder.iv_del.setAlpha(0f); } } } } } @Override public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { Log.i(TAG, "getSwipeThreshold: "); if (isUpOrDown(viewHolder.itemView)) { //如果是向上或者向下滑动 return Float.MAX_VALUE; //就返回阈值为很大 } return super.getSwipeThreshold(viewHolder); } /** * 获得逃脱(swipe)速度 * * @param defaultValue * @return */ @Override public float getSwipeEscapeVelocity(float defaultValue) { Log.d(TAG, "getSwipeEscapeVelocity: " + defaultValue); View topView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); if (isUpOrDown(topView)) { //如果是向上或者向下滑动 return Float.MAX_VALUE; //就返回阈值为很大 } return super.getSwipeEscapeVelocity(defaultValue); } /** * 获得swipe的速度阈值 * * @param defaultValue * @return */ @Override public float getSwipeVelocityThreshold(float defaultValue) { Log.d(TAG, "getSwipeVelocityThreshold: " + defaultValue); View topView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); if (isUpOrDown(topView)) { //如果是向上或者向下滑动 return Float.MAX_VALUE; //就返回阈值为很大 } return super.getSwipeVelocityThreshold(defaultValue); } /** * 判断是否是向上滑或者向下滑 */ private boolean isUpOrDown(View topView) { float x = topView.getX(); float y = topView.getY(); int left = topView.getLeft(); int top = topView.getTop(); if (Math.pow(x - left, 2) > Math.pow(y - top, 2)) { //水平方向大于垂直方向 // Log.i(TAG, "isUpOrDown: 不是"); return false; } else { return true; // Log.i(TAG, "isUpOrDown: 是"); } } }
由于我们滑动的时候会删除卡片以及数据,所以需要传入数据data和adapter,因为数据变了要调用adapter.notifyDataSetChanged()来进行界面的更新。
然后讲讲滑动Swipe和拖拽Drag的区别
Swipe是滑动,不需要长按就能触发,并且可以把子view滑出屏幕外面,滑出后会调用onSwiped方法。
Drag是拖动,需要长按才能触发,不能把子view移出屏幕外,一般用在交换子view的位置的情况下,交换位置后调用onMove方法。
所以,根据我们的需求,我们这里isLongPressDragEnabled()返回false就关闭长按拖拽,isItemViewSwipeEnabled返回true开启滑动。
getMovementFlags():这个方法是用来设置拖动和拖动的方向的,由于我们只需要滑动,所以就只设置滑动的方向,为上下左右都可以,而不需要拖动,传入0即可。
在onSwiped回调方法中有一个参数direction,通过这个参数我们就能判断是卡片是从哪个方向滑出屏幕的。
在onChildDraw方法中实现卡片的动画,以及卡片左上角和右上角的图标的透明度变化。在我们最上面的滑动卡片的时候其他卡片也在变,而且是根据滑动的情况对应着变的,所以很容易想到我们滑动最上面的卡片的时候要产生一个系数,供其他卡片变换的时候使用,这样就形成了线性关系。这个比例系数通过卡片中心点的位移距离除以整个控件(RecyclerView)宽度一半来得到,这样在我们把卡片的一半画出屏幕的时候其他卡片的动画效果达到了最大。
比例系数有了就把每一个卡片根据它所在的层数来做相应的变化就可以了,具体配合着注释看代码。
然后getSwipeThreshold,getSwipeEscapeVelocity,getSwipeVelocityThreshold,这三个函数都是用来返回对应参数的阈值的,用来控制一些触发条件的,在这每个函数中都加上判断,如果是向上或向下滑动的,就返回很大,然后就触发不了onSwipe了。
网络请求加载数据要放在onSwiped中,当判断数据还有多少条时,就异步请求数据,然后一定要放在mDatas的最后,因为只显示上面4个,所以可以像探探一样,形成永远滑不完的效果。
总结
当我们涉及到类似这种需要回收复用的功能的时候,一般都先考虑RecyclerView,不仅很好用,而且还有其他额外强大的功能,比如自定义布局管理器,item触摸监听等。还要记住一点的是,在LayoutManager中只Layout出我们需要数量的View,不要全部Layout出来。参考文献
http://blog.csdn.net/zxt0601/article/details/53730908相关文章推荐
- Android利用RecyclerView实现全选、置顶和拖拽功能示例
- 利用自定义的 RecyclerView 实现相册的滑动功能
- 利用RecyclerView的嵌套实现仿京东筛选框(文字搜索功能)
- android新特性:商城首页一键回到顶部功能实现(包括ListView, RecyclerView 和 ScrollView)
- 关于Recyclerview 实现多选,单选,全选,反选,批量删除的功能的实现
- 手把手教你实现Android RecyclerView上拉加载功能
- Material Design:利用RecyclerView CardView实现新闻卡片样式
- 利用ViewPageIndicator+ViewPager实现左右滑动带图标tab标签功能
- SearchView+RecyclerView+GreenDao的搜索功能实现(2)
- 利用AutoCompleteTextView连接到数据库实现自动提示功能
- RecyclerView中实现headerView,footerView功能
- 利用热门标签布局,实现单选列表(实现ViewGroup多行显示单选功能)
- RecyclerView利用ItemDecoration实现头部悬停效果【类似微信通讯录效果】
- android 下 利用webview实现浏览器功能
- Material Design:利用RecyclerView CardView实现新闻卡片样式
- BaseRecyclerViewAdapterHelper开源项目之BaseQuickAdapter源码学习BaseViewHolder扩展功能的实现代码学习(四)
- 实现Android RecyclerView上拉加载功能
- Android LRecyclerView实现Item侧滑菜单、长按拖拽Item、滑动删除Item等功能
- Android 整合实现简单易用、功能强大的RecyclerView
- Android一步一步带你实现RecyclerView的拖拽和侧滑删除功能