RecyclerView 整理

  • 判断是否滑动到最后 / 前的 Item
  • 添加 HeaderView 和 FooterView
  • ScrollView 嵌套情况下的问题
  • 数据源刷新的问题
  • item 局部刷新
  • 平滑的滑动置顶
  • ItemTouchHelper

参考 :
Android RecyclerView 使用完全解析 体验艺术般的控件
深入理解 RecyclerView 系列之一:ItemDecoration

开篇先说明一下,现在 RecyclerView 想必都很熟悉,我在项目里也对这个控件做过一些封装、遇到过一些问题,这里仅做记录。因为这次时间还是比较多,所以打算深入一点研究学习、内容也比较多所以分出了几篇,链接在上面。

简单初始化

//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
  • 监听 Item 的点击事件
    因为 RecyclerView 没直接的事件可以监听,需要自己去设置,
    我选择在 RecyclerView.Adapter 中设置:
//在继承了 RecyclerView.Adapter  的内部类中
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    if (holder instanceof ViewHolder){
        //设置 TextView
        holder.textView.setText(data.get(position).getTitle());
        //设置 Item 的点击事件
        holder.itemLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //自己定义的一个方法,将 position 传出去
                onItemClick(position);
            }
        });
    }
}                                                                 
//重写的 ViewHolder,使用了 ButterKnife,但逻辑上没有变化
class ViewHolder extends RecyclerView.ViewHolder{

    @BindView(R.id.news_item_title)
    TextView newsTitle;
    @BindView(R.id.news_item_img)
    ImageView newsImg;
    @BindView(R.id.news_item_layout)
    LinearLayout newsLayout;

    public ViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this,itemView);
    }
}

Item 的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/news_item_layout"
        android:layout_width="match_parent"
        android:layout_height="110dp"
        android:layout_marginBottom="2.5dp"
        android:layout_marginTop="2.5dp"
        android:elevation="4dp"
        android:background="@drawable/news_item_bg">
        <TextView
            android:textColor="#000"
            android:id="@+id/news_item_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="17dip"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="5dp"
            />
    </LinearLayout>
</LinearLayout>

还有一种方法就是使用 addOnItemTouchListener,通过触摸坐标来判断
可以参考: RecyclerView 无法添加 onItemClickListener 最佳的高效解决方案

监听滑动


//OnScrollListener  是继承自 RecyclerView.OnScrollListener 的内部类,见下文
mNewsRecyclerView.addOnScrollListener(new OnScrollListener());

监听是否滑动到底部
继承 RecyclerView.OnScrollListener,
onScrolled 获取 RecyclerView 的最后一个 Item 的位置,
onScrollStateChanged 判断当前是否滑动到最后一个 Item。

/**
  * 监听 RecyclerView 判断是否滑到最后一个 Item
  */
class OnScrollListener extends RecyclerView.OnScrollListener{

  private int lastVisibleItem;

  /**
    * 判断是否滑动了最后一个 Item
    * @param recyclerView
    * @param newState
    */
  @Override
  public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   super.onScrollStateChanged(recyclerView, newState);
   if( (newState == RecyclerView.SCROLL_STATE_IDLE)
     &&(lastVisibleItem + 1 == mRecyclerViewAdapter.getItemCount())){
    
     //newState 为 0:当前屏幕停止滚动;
     //1:屏幕在滚动且用户仍在触碰或手指还在屏幕上;
     //2:随用户的操作,屏幕上产生的惯性滑动;
     
    //在这里执行刷新/加载更多的操作
    loadMore();
}

   /**
     * 在这里获取到 RecyclerView 的最后一个 Item 的位置
     * @param recyclerView
     * @param dx
     * @param dy
     */
   @Override
   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();
  }
}

特别的是,当 RecyclerView 实现了瀑布流
就是使用了 StaggeredGridLayoutManager 的时候,因为 Item 是交错的,
所以不能使用上面的方法,而是要判断 findLastVisibleItemPosition 返回的数组的最大值,上面的 onScrolled 方法要修改为:

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);

    int [] lasPositions = new int[mStaggeredGridLayoutManager.getSpanCount()];
    int [] all = mStaggeredGridLayoutManager
                      .findLastVisibleItemPositions(lasPositions);
    lastVisibleItem = findMax(all);

}

private int findMax(int[] lastPositions) {
    int max = lastPositions[0];
    for (int value : lastPositions) {
        if (value > max) {
            max = value;
        }
    }
    return max;
}

添加 HeaderView 和 FooterView

  • 在 ViewHolder 中添加 HeaderView 和 FooterView
//当前 item 的类型,0 为头布局,1 为底部布局
public static final int TYPE_HEADER = 0;
public static final int TYPE_FOOTER = 1;
public static final int TYPE_NORMAL = 2;
//
private View mHeaderView;
private View mFooterView;
//
public void addHeaderView(View mHeaderView) {
    this.mHeaderView = mHeaderView;
}
public void addFooterView(View mFooterView) {
    this.mFooterView = mFooterView;
}
  • 通过设置 ItemViewType 来区分 HeaderView 和 FooterView
/**
 * 为每个 Item 设置类型
 * 如果头布局不为空,则将第一个 Item 设为头布局
 * @param position
 * @return
 */
@Override
public int getItemViewType(int position) {
    if (mHeaderView == null && mFooterView == null)
        return TYPE_NORMAL;
    if (mHeaderView != null && position == 0)
        return TYPE_HEADER;
    if (mFooterView != null && position == getItemCount() - 1)
        return TYPE_FOOTER;

    return TYPE_NORMAL;
}

如果 mHeaderView 不为空,则将第一个 ItemView 设为 HeaderView,
如果 mFooterView 不为空,将最后一个 ItemView 设为 FooterView。

  • 创建 ItemView
/**
 * 创建布局的时候如果发现是头布局直接返回
 * @param parent
 * @param viewType
 * @return
 */
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (mHeaderView != null && viewType == TYPE_HEADER){
        return new ViewHolder(mHeaderView);
    }
    if (mFooterView != null && viewType == TYPE_FOOTER){
        return new ViewHolder(mFooterView);
    }
    View v = LayoutInflater.from(getActivity())
            .inflate(R.layout.news_list_item,parent,false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;
}
/**
 * 只有当前的 Item 的类型是 normal 时才执行
 *  realPosition = position -1 是因为 0 被头布局占用了
 * @param holder
 * @param position
 */
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    final int realPosition;
    if (mHeaderView != null)
        realPosition = position -1;
    else
        realPosition = position;

    if (getItemViewType(position) == TYPE_NORMAL){
        if (holder instanceof ViewHolder){
            holder.newsTitle.setText(data.get(realPosition).getTitle());
            Glide.with(getActivity()).load(data.get(realPosition).getImages())
                    .into(holder.newsImg);

            //设置 Item 的点击事件
            holder.newsLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    OnItemClick(realPosition);
                }
            });
        }
    }
}
  • 相应的修改
/**
 * 根据是否有设置头布局等,判断返回的个数
 * @return
 */
@Override
public int getItemCount() {
    if (news == null)
        return 0;
    else if (mHeaderView == null && mFooterView == null)
        return news.getStories().size();
    else if (mHeaderView == null && mFooterView != null)
        return news.getStories().size() + 1;
    else if (mHeaderView != null && mFooterView == null)
        return news.getStories().size() + 1;
    else if (mHeaderView != null && mFooterView != null)
        return news.getStories().size() + 2;

    return 0;
}
//继承 RecyclerView.ViewHolder,使用 ButterKnife
class ViewHolder extends RecyclerView.ViewHolder{

    @BindView(R.id.news_item_title)
    TextView newsTitle;
    @BindView(R.id.news_item_img)
    ImageView newsImg;
    @BindView(R.id.news_item_layout)
    LinearLayout newsLayout;

    public ViewHolder(View itemView) {
        super(itemView);
        if (itemView == mHeaderView)
            return;
        if (itemView == mFooterView)
            return;
        ButterKnife.bind(this,itemView);
    }
}
  • 在 Activity 中调用
View banner = ......
mRecyclerViewAdapter.addHeaderView(banner);

关于滑动

  • 简单的直接定位:
mLayoutManager.scrollToPositionWithOffset(position, 0);

缺点是体验不好,没有滑动的过程。

  • RecyclerView 的滑动方法
rv.scrollToPosition(index);
rv.scrollBy(int x, int y);

scrollToPosition() 将对应的 item 滑动到屏幕内,当 item 变为可见时则停止滑动。
所以当指定的 item 在当前屏幕的下方时,滑动后目标 item 会出现屏幕的最低下;当指定 item 在屏幕可见时,则完全没有滑动。
很多时候这个方法是不符合我们预期的,一般是希望能将指定的 item 滑动到当前的屏幕顶端或中间。
这时候可以配合 scrollBy() 来做一个判断:

private void setSelectPosition(int index) {
    //当前可见的第一项和最后一项
    int firstItem = linearLayoutManager.findFirstVisibleItemPosition();
    int lastItem = linearLayoutManager.findLastVisibleItemPosition();
    if (index <= firstItem) {
        //当要置顶的项在当前显示的第一个项的前面时,直接调用没有问题
        rv.scrollToPosition(index);
    } else if (index <= lastItem) {
        //当要置顶的项已经在屏幕上显示时,计算需要滑动的距离
        int top = rv.getChildAt(index - firstItem).getTop();
        rv.scrollBy(0, top);
    } else {
        //当指定的 item 在当前显示的最后一项的后面时
        //这时候一次的滑动不足以将指定 item 放到顶端
        rv.scrollToPosition(index);
        //记录当前需要在RecyclerView滚动监听里面继续第二次滚动
        move = true;
    }
}
class RecyclerViewListener extends RecyclerView.OnScrollListener{
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //在这里进行第二次滚动
            if (move ){
                move = false;
                int n = mIndex - mLinearLayoutManager.findFirstVisibleItemPosition();
                if ( 0 <= n && n < mRecyclerView.getChildCount()){
                    //要移动的距离
                    int top = mRecyclerView.getChildAt(n).getTop();
                    mRecyclerView.scrollBy(0, top);
                }
            }
        }
    }

上面这个方面也是看到别人的实现的思路,虽然没什么问题不过还是算是取巧的一种方法,仅记录。
其实 Stack Overflow 上已经有了更好的答案 :RecyclerView - How to smooth scroll to top of item on a certain position?

  • 重写 LinearLayoutManager
public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {

    public LinearLayoutManagerWithSmoothScroller(Context context) {
        super(context, VERTICAL, false);
    }

    public LinearLayoutManagerWithSmoothScroller(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    //重点方法
    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                       int position) {

        //也就是说重点在于重写 SmoothScroller,而滑动的调用为 startSmoothScroll()
        RecyclerView.SmoothScroller smoothScroller = new  TopSnappedSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private class TopSnappedSmoothScroller extends LinearSmoothScroller {
        public TopSnappedSmoothScroller(Context context) {
            super(context);
        }

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return LinearLayoutManagerWithSmoothScroller.this
                    .computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference() {
            //将指定的 item 滑动至与屏幕的顶端对齐
            return SNAP_TO_START;
        }
    }
}

仔细看可以发现我们也可以选择不重写整个 LinearLayoutManager,只要将 LinearSmoothScroller 的 getVerticalSnapPreference() 重写也可以达到目的。

关于 getVerticalSnapPreference () 的源码与注释:

    /**
     * When scrolling towards a child view, this method defines whether we should align the top
     * or the bottom edge of the child with the parent RecyclerView.
     *
     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
     * @see #SNAP_TO_START
     * @see #SNAP_TO_END
     * @see #SNAP_TO_ANY
     */
    protected int getVerticalSnapPreference() {
        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
    }

返回的值决定了指定 item 的对齐方式,与顶部对齐 / 底部对齐。
所以最终代码:

//初始化过程
mLayoutManager= new LinearLayoutManager(getActivity());
mSmoothScroller = new LinearSmoothScroller(getActivity()) {
    @Override protected int getVerticalSnapPreference() {
        return LinearSmoothScroller.SNAP_TO_START;
     }

    @Override
    public PointF computeScrollVectorForPosition(int targetPosition) {
        return mLayoutManager.computeScrollVectorForPosition(targetPosition);
    }
};
mRvRetail.setLayoutManager(mLayoutManager);

//移动
mSmoothScroller.setTargetPosition(position);
mLayoutManager.startSmoothScroll(mSmoothScroller);

这样就可以实现平滑的滑动了。

与 ScrollView 嵌套时惯性滑动失效

猜测是由于滑动的时候,ScrollView 将事件分发给了 RecyclerView,所以这个时候是 rv 在滑动;
当快速滑动并离开屏幕的时候,按预期是应该有一段惯性般滑动、缓慢停止的过程,然而这时候应该是 ScrollView 拦截了这个 ACTION_UP 的事件,导致 RecyclerView 无法继续滑动。
解决这个问题可以从 RecyclerView 入手,若 RecyclerView 不再响应事件,将事件交给 ScrollView 即可:

mLayoutManager = new LinearLayoutManager(mContext){
    @Override
    public boolean canScrollVertically() {
        return false;
    }
};
mRv.setLayoutManager(mLayoutManager);

刷新数据源导致的问题

比较常见是在删除 item 的时候,可能会出现 item 的下标没有刷新、位置错位的问题。
更新了数据源之后最保险的方法是调用 notifyDataSetChanged() 去刷新所有的数据,问题是如果只更新了少量的数据或者想要保留删除/添加 item 的动画,这个方法都不能满足要求。
所以我们需要调用 notifyItemRemoved(position)notifyItemInserted(position),这时候 item 的内容已经刷新、并且带有动画效果。但是如果是删除 item 的操作,上面说的下标没有刷新、错位的问题就出现了,所以接下来要调用 notifyItemRangeChanged(position, itemSize) 将可能出现问题的 item 都刷一遍,所以 itemSize 常取 list.size()

item 的局部刷新

说起 RecyclerView 的局部刷新,一般都是说到 notifyItemChanged(int positon) 即只刷新指定的 item 的布局,但是有时候需要针对 item 的部分控件进行刷新。
比如一个 item 里有一张图片,单调用 notifyItemChanged 的时候图片会去重新加载,就导致了重复加载同样的图片而出现图片闪烁的问题,所以这时候我们需要针对 item 去刷新改变的部分、保留不变的部分。
这里用上了

@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
  super.onBindViewHolder(holder, position, payloads);
}

notifyItemChanged(int position, Object payload)
看到这两个方法其实方法已经很明显了,调用 notifyItemChanged()可以传递一个 payload 到 onBindViewHolder 方法里面,这时候就可以通过传递过来的数据类型去刷新不同的控件,没有刷新到的控件将会用 ViewHolder 的实例来展示。

ItemTouchHelper

ItemTouchHelper 是 RecyclerView 中辅助滑动和拖拽的实用工具类,一般用来做拖拽改变排序、滑动删除功能。

ItemTouchHelper helper = new ItemTouchHelper(callback);
helper.attachToRecyclerView(recyclerView);

ItemTouchHelper 的构造方法需要一个传入 ItemTouchHelper.Callback 的实例,所以重点就在于这个 Callback 里的几个方法。

class TestHelperCallback extends ItemTouchHelper.Callback{

  private ItemMoveListener mListener;

  public TestHelperCallback(){}

  public TestHelperCallback(ItemMoveListener listener){
    mListener = listener;
  }

  @Override
  public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    //两个 flags 为 0 则无法拖动
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;//允许上下滑动
    int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;//允许左右滑动
    //生成并返回
    int flags = makeMovementFlags(dragFlags, swipeFlags);
    return flags;
  }

  @Override
  public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    //拖拽的回调
    if (mListener != null){
      return mListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
    }
    return false;
  }


  @Override
  public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    //左右滑动的回调
    int position = viewHolder.getAdapterPosition();
    if (mListener != null){
      mListener.onItemDelete(position);
    }
  }

  @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);
    if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
         //左右滑动的同时改变 item 的透明度
         final float alpha = 1 - Math.abs(dX) / (float)viewHolder.itemView.getWidth();
         viewHolder.itemView.setAlpha(alpha);//透明度
         viewHolder.itemView.setTranslationX(dX);//滑动
     }
  }

  @Override
  public boolean isLongPressDragEnabled() {
    //是否可以长按拖拽
    return true;
  }
}

public interface ItemMoveListener {
   boolean onItemMove(int fromPosition, int toPosition);
   boolean onItemDelete(int position);
}

通过接口 ItemMoveListener 把回调放出去,接下来用 Adapter 实现并处理回调:

class ItemMoveRecyclerAdapter extends CommonRecyclerAdapter<String> implements ItemMoveListener{

  public ItemMoveRecyclerAdapter(Context context, List dataList, int layoutId) {
    super(context, dataList, layoutId);
  }

  @Override
  public void convert(CommonRecyclerViewHolder holder, int position, String o) {
  }

  @Override
  public void onItemClick(CommonRecyclerViewHolder holder, int position, String o) {
  }

  @Override
  public boolean onItemMove(int fromPosition, int toPosition) {
    //交换数据
    Collections.swap(mDataList, fromPosition, toPosition);
    //刷新
    notifyItemMoved(fromPosition, toPosition);
    notifyItemRangeChanged(Math.min(fromPosition,toPosition),getItemCount());
    return true;
  }

  @Override
  public boolean onItemDelete(int position) {
    mDataList.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position,getItemCount());
    return false;
  }
}

CommonRecyclerAdapter 是项目封装的 Adapter,这里不是重点。
onItemMove()中依次调用了 notifyItemMoved()notifyItemRangeChanged(),防止在移动过后 item 的下标错乱。
onItemDelete () 中同理。
上面代码基本就是拖拽排序和滑动删除的代码,因为很简单就没有多分析,因为一开始是想用 ItemTouchHelper 仿照 QQ 的侧滑按钮,但是看了一圈发现好像没办法用 ItemTouchHelper 实现。

clipToPadding 属性

android:clipToPadding 属性决定子 View 是否可以在 父布局的 padding 中绘制。
在 RecyclerView 中子 View 即 item,该属性为false 时,RecyclerView 的 padding 会被 item 遮住。
比如我们在 RecyclerView 的底部上悬浮一个按钮,按钮会挡住下方的 item,clipToPadding + paddingBottom 可以实现在 RecyclerView 拉倒底部的时候为悬浮按钮留出空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 【Android 控件 RecyclerView】 概述 RecyclerView是什么 从Android 5.0...
    Rtia阅读 306,907评论 27 440
  • 又到了更新博文的时间了,最近在看一本很不错的心理学书籍,名字叫做 《拖延心理学》,封面长下面这样子 书的内容主要是...
    ec95b5891948阅读 57,221评论 38 472
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,561评论 25 707
  • ListView作为一个流布局控件是一个比较老的资格了。RecyclerView作为Desgin设计里面的一个新空...
    mouekz阅读 461评论 0 1
  • 事实证明,感情其实很脆弱,甚至是不堪一击,唯有努力才最安心。 她是我大学里要好的朋友,但是最近遇到了感情的滑铁...
    不将就iii阅读 434评论 0 0