很久之前我看到网上写了很多关于ListView、GridView上下拉的资源,所以就萌生了自己是否能写一个上下拉。最近我看了很多关于RecyclerView的上下拉的做法,基本上都是在RecyclerView的OnScrollListener里面判断是否在头部底部,然后再给RecyclerView添加头尾部,这样的方法既简便又容易让人理解。但我今天恰恰不用这种方法去添加上下拉,我用的方法其实跟最早时候的ListView、GridView添加上下拉是一样的原理-------事件分发。
其实这种方法也比较简单,只是需要有一定事件分发的知识。那我们先讲讲原理:
原理图.png
首先我们创建两个布局文件,contentView和footerView,我们只是把contentView和footerView都放在了一个垂直分布的LinearLayout里面,然后将contentView设置为match_parent,很自然地,footerView就被挤到屏幕外面去了。然后我们就通过事件分发的机制,不断滑动的同时判断
1,滑动的状态是否是处于上拉状态;
2.RecyclerView是否已经滑到底部。
如果你理解了这样的两个简单条件,恭喜你,那你就基本搞懂了整个上下拉的原理了。
原理讲解开始前先理解几个用到的变量:
private SwipeRefreshLayout swipeRefreshLayout;
private RecyclerView recyclerView;
private TextView tvLoadingText;
//x上次保存的
private int mLastMotionX;
//y上次保存的
private int mLastMotionY;
//滑动状态
private int mPullState;
//上滑
private int PULL_UP_STATE = 2;
private int PULL_FINISH_STATE = 0;
//当前滑动的距离,偏移量
private int curTransY;
//尾部的高度
private int footerHeight;
//内容布局
private View contentView;
//尾布局
private View footerView;
private LinearLayout linearView;
//是否上拉加载更多
private boolean isLoadNext = false;
//是否在加载中
private boolean isLoading = false;
private OnSwipeRecyclerViewListener onSwipeRecyclerViewListener;
private boolean isCancelLoadNext = false;
让我们看看具体的实现代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
int x = (int)ev.getX();
int y = (int)ev.getY();
switch (ev.getAction()){
//手指触摸屏幕
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
break;
//手指移动
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastMotionX;
int deltaY = y - mLastMotionY;
//这里是判断左右滑动和上下滑动,如果是上下滑动和滑动了一定的距离,被认为是上下滑动
if(Math.abs(deltaX) < Math.abs(deltaY) && Math.abs(deltaY) > 10){
//进入条件判断,如果isRreshViewScroll返回true,则事件被拦截
if(isRefreshViewScroll(deltaY)){
return true;
}
}
break;
}
return super.onInterceptTouchEvent(ev);
}
private boolean isRefreshViewScroll(int deltaY) {
//条件1:deltaY<0,现在处于上下拉的状态;条件2:RecyclerView是否到达底部;
//条件3:curTransY<=footerHeight是下拉的偏移量;条件4:是否处于上拉或者下拉状态
if(deltaY < 0 && RecyclerViewUtil.isBottom(recyclerView) && curTransY <= footerHeight && !isLoading && !isCancelLoadNext){
//处于下拉状态
mPullState = PULL_UP_STATE;
isLoading = true;
return true;
}
return false;
}
基本上,理解了以上部分,这个上下拉的原理你算是可以毕业了,是不是很开心?让我们再往下看另外一半。
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float deltaY = y - mLastMotionY;
if(mPullState == PULL_UP_STATE){
//算出偏移量
curTransY += deltaY;
//如果偏移量大于footerView的高度,把下拉的高度赋给偏移量
if (Math.abs(curTransY) > Math.abs(footerHeight)) {
curTransY = - footerHeight;
}
//把View整体向上滑动
linearView.setTranslationY(curTransY);
if(Math.abs(curTransY) == Math.abs(footerHeight)){
isLoadNext = true;
}
}
mLastMotionY = y;
return true;
//在这里UP与CANCEL是一样的
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(isLoadNext){
//设置footerView的变化
changeFooterState(true);
mPullState = PULL_FINISH_STATE;
//设置上拉监听
if(onSwipeRecyclerViewListener != null){
onSwipeRecyclerViewListener.onLoadNext();
}else {
//如果没有拉到底,则再次把尾部隐藏
hideTranslationY();
isLoading = false;
}
}
return true;
}
return super.onTouchEvent(event);
}
好了,基本上已经完成所有的原理扫盲了,接下来就是围绕着这个原理把简单的代码补上。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefreshlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
contentView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/loading_text"
android:layout_width="match_parent"
android:layout_height="55dp"
android:gravity="center"
android:text="加载更多"
android:textColor="#000000"
android:background="#EEEEE0"/>
</LinearLayout>
footerView
public SwipeRecyclerView(Context context) {
this(context, null);
}
public SwipeRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public RecyclerView getRecyclerView(){
return recyclerView;
}
public SwipeRefreshLayout getSwipeRefreshLayout(){
return swipeRefreshLayout;
}
private void initView(Context context){
linearView = new LinearLayout(context);
linearView.setOrientation(LinearLayout.VERTICAL);
final LinearLayout.LayoutParams linearParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
addView(linearView, linearParams);
contentView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview,null);
swipeRefreshLayout = (SwipeRefreshLayout)contentView.findViewById(R.id.swiperefreshlayout);
recyclerView = (RecyclerView)contentView.findViewById(R.id.recyclerview);
footerView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview_footerview,null);
tvLoadingText = (TextView)footerView.findViewById(R.id.loading_text);
//设置SwipeRefreshLayout的监听
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (!isLoading) {
isLoading = true;
swipeRefreshLayout.setRefreshing(true);
(new Handler()).postDelayed(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(false);
if (onSwipeRecyclerViewListener != null) {
onSwipeRecyclerViewListener.onRefresh();
}
isLoading = false;
}
}, 2000);
}
}
});
linearView.addView(contentView);
linearView.addView(footerView);
//测量并设置各个布局的高度
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int height = getHeight();
if (height != 0) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
ViewGroup.LayoutParams recycleParams = contentView.getLayoutParams();
recycleParams.height = height;
contentView.setLayoutParams(recycleParams);
ViewGroup.LayoutParams footerParams =tvLoadingText.getLayoutParams();
footerHeight = footerParams.height;
ViewGroup.LayoutParams contentParams = linearView.getLayoutParams();
contentParams.height = height + footerHeight;
linearView.setLayoutParams(contentParams);
// 设置偏移量为0
curTransY = 0;
}
}
});
}
public void setSwipeRefreshColor(int color){
swipeRefreshLayout.setColorSchemeColors(getResources().getColor(color));
}
以上是初始化的相关代码
以下是一些工具方法:
//数据改变完之后,调用这方法,重置各种数值
public void onLoadFinish(){
if(curTransY == 0){
return;
}
isLoading = false;
hideTranslationY();
}
//用动画的方式把footerView隐藏
private void hideTranslationY() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(linearView, "translationY",curTransY, 0).setDuration(1000);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.start();
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
curTransY = 0;
changeFooterState(false);
}
});
}
private void changeFooterState(boolean loading){
if(loading){
tvLoadingText.setText("正在努力的加载中...");
}
else{
tvLoadingText.setText("加载更多");
}
}
public void setOnSwipeRecyclerViewListener(OnSwipeRecyclerViewListener onSwipeRecyclerViewListener){
this.onSwipeRecyclerViewListener = onSwipeRecyclerViewListener;
}
public interface OnSwipeRecyclerViewListener{
public void onRefresh();
public void onLoadNext();
}
接下来看看使用:
有兴趣的同学可以去下载源码去研究一下,以上我还有一个判断RecyclerView各种状态类似于判断是否到顶部或者底部的工具类,是由我有个很好人的师兄提供给我的,他也在我写代码的时候给我提供很多很好的建议。慢慢地,我觉得一些自定义ViewHolder的原理并没有想象中的难,只有你是否肯花时间去研究。如果对这个上下拉有什么意见可以跟我讨论,一起提高!
源码下载链接
网友评论