1. 前言
SwipeRefreshLayout是google出的一个支持下拉更新,上滑加载的控件,有如下两个问题:
1)上滑加载数据需要我们自己实现;
2)loading动画会随着手势向下移动,其实好多情况下我们可能需要固定顶部的loading动画。
针对上面第一个问题,SuperSwipeRefreshLayout是基于SwipeRefreshLayout,实现了上滑的数据加载部分,并对不同的控件,如ListView,RecyclerView等做了很好的封装,项目github地址: SuperSwipeRefreshLayout-Demo。
SuperSwipeRefreshLayout里面没有设置loading动画是否随手势移动的API,因此没有解决第二个问题。TRecyclerView为解决上面两个问题,处理了上滑数据加载的逻辑,同时固定loading动画在顶部位置,下面主要讲讲TRecyclerView的具体实现方式。
2. TRecyclerView
1) TRecyclerView 的结构
TRecycleView是一个FrameLayout主要包括两部分,Header View和RecycleView,而RecycleView的View类型大体分为两部分:Normal View和Footer View。
TRecyclerView中有一个TRecyclerAdapter,是用来加载RecyclerView的Item View,是TRecyclerView中真正加载数据的Adapter,其中包括两大类的数据类型,即正常的Normal View和Header View,Normal View是通过RecyclerView.Adapter来加载,就是我们需要写的Adapter。
2)TRecyclerView的初始化
下面结合TRecyclerView的结构图,我们看看具体的代码实现,首先是TRecycleView的构造方法:
public TRecyclerView(Context context) {
super(context);
init(context);
}
private void init(Context ctx) {
mCtx = ctx;
mTouchSlop = ViewConfiguration.get(mCtx).getScaledTouchSlop();
initView();
}
private void initView() {
//创建一个Header View
createHeaderView();
addTargetView();
linearLayoutManager = new LinearLayoutManager(mCtx);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setVerticalScrollBarEnabled(true);
initListener();
}
//Header View是一个RelativeLayout,同时往Header View里面添加一个自己的Header,我们可以自定义样式
private void createHeaderView() {
mHeaderContainer = new RelativeLayout(mCtx);
mHeaderHeight = (int)mCtx.getResources().getDimension(R.dimen.header_height);
mTipHeight = (int)mCtx.getResources().getDimension(R.dimen.header_tip_height);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderHeight);
params.gravity = Gravity.TOP;
addView(mHeaderContainer, params);
setHeaderView();
}
//设置自己的Header
private void setHeaderView(){
mHeaderHolder = new HeaderHolder(mCtx);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, mHeaderHeight);
mHeaderContainer.addView(mHeaderHolder.getHeaderView(), params);
mHeaderHolder.setAnimListener(mAnimListener);
}
//添加一个RecycleView,因为TRecycleView是一个FrameLayout,因此,RecycleView是处在在Header View的上面
private void addTargetView(){
mRecyclerView = new RecyclerView(mCtx);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addView(mRecyclerView, params);
}
3)TRecyclerAdapter的实现
我们知道TRecyclerView中真正加载数据的Adapter是TRecyclerAdapter,我们看看TRecyclerView设置RecyclerView.Adapter的API,代码如下:
public void setAdapter(RecyclerView.Adapter adapter){
adapter.registerAdapterDataObserver(mDataObserver);
mTAdapter = new TRecyclerAdapter(mCtx, adapter);
mRecyclerView.setAdapter(mTAdapter);
}
我们给RecyclerView.Adapter注册了一个观察者,调用RecyclerView.Adapter的数据更新方法时,会通知TRecyclerAdapter去更新数据数据,代码如下:
private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mTAdapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mTAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mTAdapter.notifyItemRangeChanged(positionStart , itemCount, payload);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mTAdapter.notifyItemRangeInserted(positionStart , itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mTAdapter.notifyItemRangeRemoved(positionStart , itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mTAdapter.notifyItemMoved(fromPosition, toPosition );
}
};
再看看TRecyclerAdapter的onCreateViewHolder和onBindViewHolder方法的实现。
onCreateViewHolder方法:
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return buildHolder(parent, viewType);
}
private RecyclerView.ViewHolder buildHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder holder = null;
switch (viewType) {
case ITEM_TYPE_FOOTER:
//Footer View的类型
holder = new BaseViewHolder(mFooterHolder.getFooterView());
break;
default:
//Normal View 的类型
holder = mAdapter.onCreateViewHolder(parent, viewType);
break;
}
return holder;
}
@Override
public int getItemViewType(int position) {
if (isFooter(position)) {
//底部View
return ITEM_TYPE_FOOTER;
} else {
return mAdapter.getItemViewType(position);
}
}
onBindViewHolder方法:
//如果是Footer View类型,则直接返回,否则调用mAdapter的onBindViewHolder方法
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (isFooter(position)) {
return;
}
initData(holder, position);
}
private void initData(RecyclerView.ViewHolder holder, final int position) {
final int type = getItemViewType(position);
if (type != ITEM_TYPE_FOOTER) {
mAdapter.onBindViewHolder(holder, position);
}
}
4)TRecyclerView下拉更新数据
TRecycleView下拉超过一定的高度,这个高度是Header View的高度,松开手后开始加载数据,下面看看TRecyclerView的onInterceptTouchEvent方法和onTouchEvent方法。
onInterceptTouchEvent(MotionEvent ev) 方法:
ublic boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
//如果RecyclerView能够向下或者向上滚动,则不拦截事件,事件交给RecyclerView处理
if(isUnIntercept()){
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsDrag = false;
mInitY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float y = ev.getY();
//如果TRecyclerView下拉超过一定距离,则认为TRecyclerView是在被拖拽,同时设置mIsDrag标志为true
if(y-mInitY >= mTouchSlop && !mIsDrag){
mIsDrag = true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsDrag = false;
break;
default:
break;
}
return mIsDrag;
}
onTouchEvent(MotionEvent event) 方法:
public boolean onTouchEvent(MotionEvent event) {
if(isUnIntercept()){
return false;
}
float dist = 0f;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsDrag = false;
break;
case MotionEvent.ACTION_MOVE:
float y = event.getY();
dist = (y - mInitY)* TRecycleViewConst.PULL_DRAG_RATE;
if(dist > 0){
//下拉移动TRecyclerView
mRecyclerView.setTranslationY(getY() + dist);
}
if(mIsDrag){
//下拉距离超过Header View的高度,则松手后可以刷新数据
if(mPullRefresh != null){
mPullRefresh.pullRefreshEnable(dist >= mHeaderHeight);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
dist = (event.getY() - mInitY) * TRecycleViewConst.PULL_DRAG_RATE;
if(mIsDrag){
//松手后,如果下拉距离超过Header View的高度,则执行回弹到HeaderView位置的动画,动画结束后,调用刷新数据的方法;否则执行回弹到初始位置的动画
if(dist >= mHeaderHeight){
animToHeader();
}else{
animToStart();
}
}
mIsDrag = false;
break;
}
return true;
}
animToHeader方法:
private void animToHeader(){
ObjectAnimator animator = ObjectAnimator.ofFloat(mRecyclerView,"translationY", mHeaderHeight);
animator.addListener(mToHeaderListener);
animator.setDuration(AnimDurConst.ANIM_TO_HEADER_DUR);
animator.start();
}
private Animator.AnimatorListener mToHeaderListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//回弹动画结束后,开始调用pullRefresh的回调,在这里面可以加载网络数据
if(mPullRefresh != null){
mRefresh = true;
mPullRefresh.pullRefresh();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
5)TRecyclerView上滑加载数据
上面讲了TRecyclerView下拉更新数据的过程,下面讲讲TRecyclerView上滑加载数据的实现。
看上面的结构图,我们知道Footer View并不是直接作为TRecyclerView的一个View,而是RecyclerView的一个Item View,因此,当RecyclerView上滑到最后一个Item View,即Footer View可见时,我们可以处理上滑加载数据的逻辑,代码的实现如下:
private void initListener(){
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//如果RecyclerView的Scroll State是IDLE,我们判断下RecyclerView是否已经滑动到底部,如果是则执行loadMore方法回调
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (targetInBottom()) {
if(mPushRefresh != null){
mLoadMore = true;
mPushRefresh.loadMore();
}
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
//滑动到底部,且最后一个元素可见,则认为到达底部
private boolean targetInBottom() {
if (targetInTop()) {
return false;
}
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
int count = mRecyclerView.getAdapter().getItemCount();
if (layoutManager instanceof LinearLayoutManager && count > 0) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.findLastVisibleItemPosition() == count - 1) {
return true;
}
}
return false;
}
3. TRecyclerView实例
我们自个写了一个NewsRecyclerAdapter,通过TRecyclerView实行了数据的下拉更新,上滑加载的逻辑。
下拉更新数据:
上滑加载数据:
TRecyclerView项目地址:TRecyclerView。
4. 总结
TRecyclerView还存如下一些问题,后续会优化。
1)TRecyclerView中的RecyclerView没有滚动条,这是因为我们直接new RecyclerView,RecyclerView的一些初始化方法没有执行到。
解法方法:RecyclerView通过inflate的方式去加载一个xml文件。
2)TRecyclerView在下拉加载数据的时候,可以再次下拉刷新数据,这个后续会优化。
3)TRecyclerView有一些业务逻辑的耦合,如tips的显示动画。
TRecyclerView项目地址:TRecyclerView。