IRcyclerView从会用到会写系列的第二篇,上一篇我们讲了怎么使用IRrecyclerView,这一节我们来看看,IRecyclerView到底是怎样实现效果的。
首先,你得要有一个猜想
首先,我们会很疑惑,感觉IRecyclerView用起来和recyclerview没什么区别,就只是setAdapter变成了setIAdapter。那么,我们就从setIAdapter开始看起。
点进去,我们看见的setIAdapter的方法,以及现在可以看到IRecyclerView是继承自RecyclerView的,所以拥有recyclerView的所有方法。
public void setIAdapter(Adapter adapter) {
this.ensureRefreshHeaderContainer();
this.ensureHeaderViewContainer();
this.ensureFooterViewContainer();
this.ensureLoadMoreFooterContainer();
this.setAdapter(new WrapperAdapter(adapter, this.mRefreshHeaderContainer, this.mHeaderViewContainer, this.mFooterViewContainer, this.mLoadMoreFooterContainer));
}
噫!!上面的都是些什么鬼!最后还是调用了setAdapter,只不过却传了一个我们不知道的参数。而且上面的名字很熟悉,明显就是我们的刷新item,头结点,尾节点,和加载item。
嘛嘛,我们开始猜想,他是不是其实已经早就已经将我们所需要的四个动态item早就加载了list里面,但是返回给我们的就只是我们设置的参数。
我们随便点进去一个会发现,噫!这不就是一个空的viewlayout嘛。
private void ensureRefreshHeaderContainer() {
if(this.mRefreshHeaderContainer == null) {
this.mRefreshHeaderContainer = new RefreshHeaderLayout(this.getContext());
this.mRefreshHeaderContainer.setLayoutParams(new LayoutParams(-1, 0));
}
}
再看看他们的定义
private RefreshHeaderLayout mRefreshHeaderContainer;
private FrameLayout mLoadMoreFooterContainer;
private LinearLayout mHeaderViewContainer;
private LinearLayout mFooterViewContainer;
就更加坚定了我们的之前的猜想,这些就是viewLayout。但是他们却传给了一个不知姓名的WrapperAdapter,所以为什么我们设置adapter的时候,却和往常的一样,没有因为多加了几个item而是item的顺序发生了错乱, 所以 ,这个WrapperAdapter一定有鬼,而且他最终肯定也是一个adapter,所噶,那我们进去看看这个WrapperAdapter到底是什么东西。
public WrapperAdapter(Adapter adapter, RefreshHeaderLayout refreshHeaderContainer, LinearLayout headerContainer, LinearLayout footerContainer, FrameLayout loadMoreFooterContainer) {
this.mAdapter = adapter;
this.mRefreshHeaderContainer = refreshHeaderContainer;
this.mHeaderContainer = headerContainer;
this.mFooterContainer = footerContainer;
this.mLoadMoreFooterContainer = loadMoreFooterContainer;
this.mAdapter.registerAdapterDataObserver(this.mObserver);
}
点进去我们就看见了WrapperAdapter的构造函数, 前面5行不用解释了吧,就是对象变量的初始化。重点是最后一个,我滴天!!!registerAdapterDataObserver()这个是什么东东,而且还传入了一个自定义的mOberserver。
好了!到了这里我们就又要科普一下了!这个registerAdapterDataObserver是个什么东东。让我们看一下官方的说法:
Register a new observer to listen for data changes.
嘛嘛!什么意思呢!就是说注册一个新的监听者,去监听data的变化。我们大家都知道,当我们使用recyclerview的过程中,如果数据变化了,我们就要使用notifydatachange去改变recycleview中的item的数量以及顺序。而具体要怎么变,其实也就是通过adapter中的observer接收到的消息,然后具体在改变。
OK!科普结束。
那我们就去看看mObserver是怎样响应这个data变化的消息事件。
image.png
OK,我们可以看见,我们新建的adapterdataobserver里面重写了所有的方法。
这里我们打开几个重点的,有代表性的函数。
public void onItemRangeChanged(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeChanged(positionStart + 2, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
WrapperAdapter.this.notifyItemRangeInserted(positionStart + 2, itemCount);
}
我们发现,噫!!为什么在更新数据的时候,要向后移动两个位置呢??咦咦!!!这部又进一步证明我们的猜想嘛,因为那两个是refreshview和handView,而他们本来就已经先建好了的,只是需要动态的设置才能显示出来。
然后我们继续往下翻,哇塞,心里的石头落地,这不就是印证了我们的猜想。根据不同的类型,创建不同的itemview。
image.png public void onBindViewHolder(ViewHolder holder, int position) {
if(1 < position && position < this.mAdapter.getItemCount() + 2) {
this.mAdapter.onBindViewHolder(holder, position - 2);
}
}
所以我们的猜想是正确的,irecyclerview内部自己实现了adapter,已经将我们可能需要到的四个view已经加了进去。只是暂时让我们看不见而已。既然我们已经知道这一点了,那么,开始下一步,我什么我们看不见他们!
为什么我们看不见他们
让我们回到IRecyclerView文件中,看看那itemlayout都是怎么定义的。
private void ensureRefreshHeaderContainer() {
if (mRefreshHeaderContainer == null) {
mRefreshHeaderContainer = new RefreshHeaderLayout(getContext());
mRefreshHeaderContainer.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
}
}
private void ensureLoadMoreFooterContainer() {
if (mLoadMoreFooterContainer == null) {
mLoadMoreFooterContainer = new FrameLayout(getContext());
mLoadMoreFooterContainer.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
看到了吧,在初始化他的时候,我们给他的高度为0,或者,我们给他们的高度为自适应,而开始的时候,他们里面都是没有子view的,所以我们看不见他们,他们开始时候的高度为0。
itemView的动态改变?不存在的
其实到这里,我们已经能够知道了头节点和尾节点的实现方式,然而我们现在关心的是下拉刷新的效果以及,上拉加载更多的页面的变化。
首先,让我们想一下,如果是你,想要实现下拉刷新,你会怎么做,一般的想法就是当我们的listview已经处于在顶端的时候,你还在继续下拉的话,那么,我们的refreshView是不是就应该出现了,所以说,你肯定需要检测到我们下拉的手势,所以,我们需要重写onTouchEvent方法。
所以,继续看源码,我们果然发现了他在确实是重写了onTouchEvent方法。并且,里面的东西还挺多,我们挑重点的来说。
case MotionEvent.ACTION_MOVE: {
...
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
//保证 存在 开启 存在刷新页面 手指滑动 保证在最头上
final boolean triggerCondition = isEnabled() && mRefreshEnabled && mRefreshHeaderView != null && isFingerDragging() && canTriggerRefresh();
if (triggerCondition) {
...
//下拉
if (dy > 0 && mStatus == STATUS_DEFAULT) {
...
}//上拉
else if (dy < 0) {
...
}
//下拉或者是放松状态
if (mStatus == STATUS_SWIPING_TO_REFRESH || mStatus == STATUS_RELEASE_TO_REFRESH) {
...
fingerMove(dy);//变化的Y值
...
}
}
}
break;
在move事件中,看见,通过每次对比dy,得到是否是上划还是下划,然后添加不同的状态,最后调用fingerMove。还有一个重点就是triggerCondition,他是决定该事件能否开始我们下拉刷新的操作。我们看看最后两个条件的函数。
//判断当前是否是 拖 这个状态
private boolean isFingerDragging() {
return getScrollState() == SCROLL_STATE_DRAGGING;
}
//当前是否已经到达recyclerView的顶部
public boolean canTriggerRefresh() {
final Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0) {
return true;
}
View firstChild = getChildAt(0);
int position = getChildLayoutPosition(firstChild);
if (position == 0) {
if (firstChild.getTop() == mRefreshHeaderContainer.getTop()) {
return true;
}
}
return false;
}
秒懂对不对!!!再来看看fingerMove是什么操作。因为fingerMov最终调用了move。move如下:
private void move(int dy) {
if (dy != 0) {
int height = mRefreshHeaderContainer.getMeasuredHeight() + dy;
setRefreshHeaderContainerHeight(height);
mRefreshTrigger.onMove(false, false, height);
}
}
好的 我相信你已经也懂了对不对。
最后是setRefreshHeaderContainerHeight,他就动态改变了refreshView的高度。也就是我们下拉时候,把refreshView拉出来的操作。
private void setRefreshHeaderContainerHeight(int height) {
mRefreshHeaderContainer.getLayoutParams().height = height;
mRefreshHeaderContainer.requestLayout();
}
最后回到我们的ontouchEvent,它里面还有两个事件:
case MotionEvent.ACTION_UP: {
onFingerUpStartAnimating();
}
break;
case MotionEvent.ACTION_CANCEL: {
onFingerUpStartAnimating();
}
当触摸事件终止和触摸事件手指抬起的时候,开始动画效果。
嘛嘛!我们已经知道,在我们拉完了之后,页面向上弹起,其实也就是动画效果实现了。
private void onFingerUpStartAnimating() {
if (mStatus == STATUS_RELEASE_TO_REFRESH) {
startScrollReleaseStatusToRefreshingStatus();
} else if (mStatus == STATUS_SWIPING_TO_REFRESH) {
startScrollSwipingToRefreshStatusToDefaultStatus();
}
}
到这里 ,我想大家也已经明白了IRecyclerView大致的道理。
1.重写了recyclerView,将其多增加了四个节点,作为我们后期动态改变的itemview
2.在touchEvent中,定了我们特定的事件,实现下拉效果的refreshView的动态改变
3.在手指抬起,或者触摸事件终止的时候,通过动画,实现页面的反弹回去。
好的!!!接下来我们开始重复造轮子,自己仿照这IRecyclerView的方式,自己写一个IRecyclerViewCopy,并在内部增加几个常见的示例,不用每次使用的时候,还要自己写页面动画效果。那么今天就这样咯!!hhhhhhhh
网友评论