SwipeRefreshLayout与RecyclerView的巧夺天工

nfkj2645 9年前

来自: http://blog.csdn.net/liyuanjinglyj/article/details/50569814


平常开发我们需要使用ListView下拉刷新或者其上滑加载的时候,不是自己写就是用别人写好了,但是编程中有一点是不变的,就是一般封装好的东西,其扩展性极低,比如你使用xutils,imageloader等开源框架的时候,它允许你扩展吗?答案当然是否,那我想要实现自己非常酷酷的ListView时候,只有自己动手实现。不过,谷歌在2015在v4开发包加入 豪华套餐SwipeRefreshLayout供你享用。



1.SwipeRefreshLayout使用注意说明



SwipeRefreshLayout默认只能包含一个滑动控件,比如本文使用的RecyclerView。


㈡一般使用ListView组件都有一个需求,那么就是没有网络的情况下,将显示其他控件提示用户加载失败或者需要联网。那么,SwipeRefreshLayout可以包含有且仅有一个布局,布局里面可以添加你需要的控件。


㈢如果你按㈡这样做,那么SwipeRefreshLayout默认只会监听一个滑动控件,当你有多个控件的时候会使其找不到监听的滑动控件。这样SwipeRefreshLayout功能就不复存在了。


㈣那么今天我们将实现的下拉刷新和上滑加载该怎么办呢?答案就是重写SwipeRefreshLayout。



2.重写SwipeRefreshLayout



当我们重写SwipeRefreshLayout,需要使用到如下一个方法:


㈠canChildScrollUp


我们来看看其文档说明:


public boolean canChildScrollUp ()
Returns
Whether it is possible for the child view of this layout to scroll up. Override this if the child view is a custom view.


如果子视图为自定义视图那么必须重写该方法。同理,当你的子视图用布局包裹的时候,其就是你自定义的,除非你的子视图只有ListView,当有多个控件时候,其默认找不到ListView监听其滑动事件,必须重写该方法。

 

㈡重写SwipeRefreshLayout


既然找不到该子视图,那么就必须传入子视图的控件,以监听其滑动状态,也就是自定义一个属性:


<declare-styleable name="LYJSwipeLayoutAttrs">      <attr name="scrollableChildId" format="reference" />  </declare-styleable>

下面源码是怎么写的canChildScrollUp:


public boolean canChildScrollUp() {      if (android.os.Build.VERSION.SDK_INT < 14) {          if (mTarget instanceof AbsListView) {              final AbsListView absListView = (AbsListView) mTarget;              return absListView.getChildCount() > 0                      && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)                              .getTop() < absListView.getPaddingTop());          } else {              return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;          }      } else {          return ViewCompat.canScrollVertically(mTarget, -1);      }  }

下面我们来依葫芦画瓢重写SwipeRefreshLayout:


public class LYJSwipeRefreshLayout extends SwipeRefreshLayout {      private static final String TAG = LYJSwipeRefreshLayout.class.getCanonicalName();      private int mScrollableChildId;//控件ID      private View mScrollableChild;//子控件      public LYJSwipeRefreshLayout(Context context) {          this(context, null);      }      public LYJSwipeRefreshLayout(Context context, AttributeSet attrs) {          super(context, attrs);          //获取监听子控件的ID          TypedArray a = context.obtainStyledAttributes(                  attrs, R.styleable.LYJSwipeLayoutAttrs);          mScrollableChildId = a.getResourceId(R.styleable.LYJSwipeLayoutAttrs_scrollableChildId, 0);          mScrollableChild = findViewById(mScrollableChildId);          a.recycle();      }      @Override      public boolean canChildScrollUp() {          //判断有没有传入子控件          ensureScrollableChild();          if (android.os.Build.VERSION.SDK_INT < 14) {              if (mScrollableChild instanceof AbsListView) {                  final AbsListView absListView = (AbsListView) mScrollableChild;                  return absListView.getChildCount() > 0                          && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)                          .getTop() < absListView.getPaddingTop());              } else {                  return mScrollableChild.getScrollY() > 0;              }          } else {              return ViewCompat.canScrollVertically(mScrollableChild, -1);          }      }      private void ensureScrollableChild() {          if (mScrollableChild == null) {              mScrollableChild = findViewById(mScrollableChildId);          }      }  }

布局文件如下:


<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:orientation="vertical">        <android.support.v7.widget.Toolbar          android:id="@+id/activity_main_toolbar"          android:layout_width="match_parent"          android:layout_height="?attr/actionBarSize"          android:background="@color/activity_main_tablayout_bg">            <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:layout_centerInParent="true"              android:layout_gravity="center"              android:text="@string/app_name"              android:textColor="@android:color/black"              android:textSize="20sp"              android:textStyle="bold" />        </android.support.v7.widget.Toolbar>          <com.example.liyuanjing.welltestdemo.LYJSwipeRefreshLayout          xmlns:app="http://schemas.android.com/apk/res-auto"          android:id="@+id/activity_main_swipe"          android:layout_width="match_parent"          android:layout_height="match_parent"          app:scrollableChildId="@+id/activity_main_recyclerview"          android:background="@android:color/transparent">            <FrameLayout              android:layout_width="match_parent"              android:layout_height="wrap_content">                <android.support.v7.widget.RecyclerView                  android:id="@+id/activity_main_recyclerview"                  android:layout_width="match_parent"                  android:layout_height="match_parent"                  android:cacheColorHint="@null"                  android:scrollbars="vertical" />                <LinearLayout                  android:id="@+id/activity_main_linearlayout"                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"                  android:orientation="vertical"/>            </FrameLayout>        </com.example.liyuanjing.welltestdemo.LYJSwipeRefreshLayout>    </LinearLayout>

红色标记的为传入子控件ID的属性。这样SwipeRefreshLayout就可以监听recyclerview了。



3.实现下拉刷新,上滑加载


 

为了代码的重用效率高,我写了一个基类BaseActivity:


public abstract class BaseActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {      /***  * 处理下拉和刷新滴  */  protected SwipeRefreshLayout swipeRefreshLayout;      /***  * 进化的ListView  */  protected RecyclerView recyclerView;      /***  * 该布局在没有网络的时候,显示的布局  */  protected LinearLayout linearLayout;      /***  * RecyclerView的样式(网格,瀑布,线性)  */  protected LinearLayoutManager mLayoutManager;      /***  * 记录最后一项的位置  */  protected int lastVisibleItem=0;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          this.swipeRefreshLayout=(SwipeRefreshLayout)findViewById(R.id.activity_main_swipe);          this.recyclerView=(RecyclerView)findViewById(R.id.activity_main_recyclerview);          this.linearLayout=(LinearLayout)findViewById(R.id.activity_main_linearlayout);          initView();          recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {              @Override              public void onScrolled(RecyclerView recyclerView, int dx, int dy) {                  super.onScrolled(recyclerView, dx, dy);                  lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();              }                @Override              public void onScrollStateChanged(RecyclerView recyclerView, int newState) {                  super.onScrollStateChanged(recyclerView, newState);                  onRecyclerViewStateChanged(newState);              }          });      }        @Override      public void onRefresh() {          onRecyclerViewRefresh();      }        /***  * 初始化界面  */  public abstract void initView();        /***  * 监听RecyclerView滑动事件  * @param newState 滑动状态  */  public abstract void onRecyclerViewStateChanged(int newState);        /***  * 下拉刷新处理  */  public abstract void onRecyclerViewRefresh();  }

注释非常明确,布局中有一个linearlayout其中无任何控件,是为了扩展任何你需要的无网络时显示的界面的。你只需要继承该类实现这几个抽象方法。


㈠自定义适配器


代码如下:


public class LYJRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {      /***  * 每项的数据集合  */  private List<String> messageItems;      /***  * 监听item点击事件。  */  private LYJItemClickListener mItemClickListener;      /***  * 一共显示多少条数据  */  private int totalSize;        public LYJRecyclerViewAdapter(List<String> messageItems,int size){          this.messageItems=messageItems;          this.totalSize=size;      }        /***  * 监听点击事件接口  */  public interface LYJItemClickListener {          public void onItemClick(View view, int postion);      }        /***  * 设置item点击事件  * @param listener  */  public void setOnItemClickListener(LYJItemClickListener listener) {          this.mItemClickListener = listener;      }      @Override      public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {          if (i == Constants.TYPE_ITEM) {              View view = LayoutInflater.from(viewGroup.getContext()).inflate(                      R.layout.activity_main_adapter_item, null);              view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                      ViewGroup.LayoutParams.WRAP_CONTENT));              return new ItemViewHolder(view,this.mItemClickListener);          }          //滑动到底部返回footview          else if (i == Constants.TYPE_FOOTER) {              View view = LayoutInflater.from(viewGroup.getContext()).inflate(                      R.layout.activity_main_adapter_footview, null);              view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                      ViewGroup.LayoutParams.WRAP_CONTENT));              return new FooterViewHolder(view);          }          return null;      }        @Override      public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {          if(viewHolder instanceof ItemViewHolder){              ((ItemViewHolder) viewHolder).name.setText(messageItems.get(i));          }else{              if(this.totalSize==(getItemCount()-1)){                  ((FooterViewHolder)viewHolder).flagTxt.setText("已经加载完全部内容");              }else{                  ((FooterViewHolder)viewHolder).flagTxt.setText("正在加载中........");              }          }      }        @Override      public int getItemCount() {          return messageItems.size()+1;//加1是多的footview那一项,也就是滑动到footview就加载,而不是最后数据项。      }        @Override      public int getItemViewType(int position) {          if (position + 1 == getItemCount()) {              return Constants.TYPE_FOOTER;          } else {              return Constants.TYPE_ITEM;          }      }        /***  * 底部布局  */  public class FooterViewHolder extends RecyclerView.ViewHolder {          private TextView flagTxt;          public FooterViewHolder(View itemView) {              super(itemView);              this.flagTxt=(TextView)itemView.findViewById(R.id.activity_main_adapter_footview_txt);          }      }        /***  * 数据项布局  */  class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {          private TextView name;          private LYJItemClickListener mListener;//设置点击事件            public ItemViewHolder(View itemView, LYJItemClickListener listener) {              super(itemView);              this.name = (TextView) itemView.findViewById(R.id.activity_main_adapter_item_name);              this.mListener = listener;              itemView.setOnClickListener(this);//设置点击事件          }            @Override          public void onClick(View v) {              if (mListener != null) {                  mListener.onItemClick(v, getPosition());              }            }      }  }

㈡MainActivity的实现


其继承自BaseActivity,并且实现item点击事件接口LYJRecyclerViewAdapter.LYJItemClickListener:


public class MainActivity extends BaseActivity implements LYJRecyclerViewAdapter.LYJItemClickListener{      /***  * 数据项  */  private List<String> messageItems=new ArrayList<>();      /***  * 自定义adapter  */  private LYJRecyclerViewAdapter adapter;      /***  * 获取资源文件字符串中间转换集合  */  private List<String> strFlag;      @Override      public void initView() {          Toolbar toolbar=(Toolbar)findViewById(R.id.activity_main_toolbar);          toolbar.setTitle("");          setSupportActionBar(toolbar);          this.swipeRefreshLayout.setColorSchemeColors(Color.RED);//设置加载内圈颜色          this.swipeRefreshLayout.setOnRefreshListener(this);//设置下拉刷新事件          this.swipeRefreshLayout.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.activity_main_tablayout_bg));//设置加载外圈颜色          // 这句话是为了,第一次进入页面的时候显示加载进度条          swipeRefreshLayout.setProgressViewOffset(false, 0, (int) TypedValue                  .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()                          .getDisplayMetrics()));          mLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);//设置布局样式          recyclerView.setHasFixedSize(true);          recyclerView.setLayoutManager(mLayoutManager);          loadingRecyclerView(Constants.LISTVIEW_INIT);//初始化RecyclerView      }        @Override      public void onRecyclerViewStateChanged(int newState) {          if (messageItems == null || messageItems.size() <= 0) {              Snackbar.make(swipeRefreshLayout, "没有数据得先下拉刷新", Snackbar.LENGTH_SHORT).show();              return;          }          //滚动事件结束并且到达最底端          if (newState == RecyclerView.SCROLL_STATE_IDLE  && lastVisibleItem + 1 == adapter.getItemCount()) {              loadingRecyclerView(Constants.LISTVIEW_DOWNLOAD);//下滑RecyclerView          }      }        @Override      public void onRecyclerViewRefresh() {          loadingRecyclerView(Constants.LISTVIEW_REFRESH);//下拉刷新RecyclerView      }      public void loadingRecyclerView(int recyclerViewState){          swipeRefreshLayout.setRefreshing(true);//打开加载动画          if (!LYJNetwork.isNetworkAvailable(MainActivity.this)) {              Snackbar.make(swipeRefreshLayout, "没有网络你逗我玩啊?", Snackbar.LENGTH_SHORT).show();              swipeRefreshLayout.setRefreshing(false);//没有网络时候直接关闭加载动画              return;          }          //当为初始化的时候          if(recyclerViewState==Constants.LISTVIEW_INIT){              addStringToList();              adapter=new LYJRecyclerViewAdapter(messageItems,100);              recyclerView.setAdapter(adapter);          }else if(recyclerViewState==Constants.LISTVIEW_REFRESH){              //当为下拉刷新的时候              messageItems.clear();              addStringToList();              recyclerView.setAdapter(null);              adapter = new LYJRecyclerViewAdapter(messageItems,100);              adapter.setOnItemClickListener(MainActivity.this);              recyclerView.setAdapter(adapter);          }else{              //当为下滑加载的时候              if(messageItems.size()!=100){                  addStringToList();                  adapter.notifyDataSetChanged();              }          }          swipeRefreshLayout.setRefreshing(false);//执行完成也要关闭加载动画      }        @Override      public void onItemClick(View view, int postion) {          //每项的点击事件      }        //模拟增加数据      public void addStringToList(){          strFlag= Arrays.asList(getResources().getStringArray(R.array.welltest_array_string));          for(int i=0;i<strFlag.size();i++){              messageItems.add(strFlag.get(i));          }      }  }

这样谷歌官方控件的下拉刷新,上滑动加载就完成了。


从这里可以看到,虽然说ListView有点击事件,有许多扩展,但你想扩展ListView就必须重构很多地方。而RecyclerView,虽然什么都没有,但你扩展起来要方便的多。这就是从0开始的优势。当一个框架继承了很多很多东西,那么你要修改其中的东西,那么就是牵一发而动全身。没有最适合的框架,只有最优解。


看看最后实现的效果: