SwipeRefreshLayout與RecyclerView的巧奪天工
來自: 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開始的優勢。當一個框架繼承了很多很多東西,那么你要修改其中的東西,那么就是牽一發而動全身。沒有最適合的框架,只有最優解。
看看最后實現的效果:
