package com.linroid.sky31radio.view; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; import com.linroid.sky31radio.R; import timber.log.Timber; /** * Created by linroid on 2/3/15. */ public class ContentLoaderView extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener { public static final int LOAD_MORE_ITEM_SLOP = 4; View loadingView; View emptyView; View errorView; Button emptyRetryBtn; Button errorRetryBtn; TextView emptyMessageTV; TextView errorMessageTV; SwipeRefreshLayout refreshLayout; RecyclerView recyclerView; private View contentView; private OnRefreshListener refreshListener; private OnMoreListener moreListener; private boolean loadMore = false; private int totalPage = 1; private int currentPage = 1; private int padding; private int paddingLeft; private int paddingRight; private int paddingTop; private int paddingBottom; private boolean clipToPadding; public static final String DISPLAY_STATE = "display_state"; public static final int STATE_CONTENT = 0x1; public static final int STATE_LOADING = 0x2; public static final int STATE_EMPTY = 0x3; public static final int STATE_ERROR = 0x4; private int displayState = STATE_LOADING; public ContentLoaderView(Context context) { super(context); initViews(); } public ContentLoaderView(Context context, AttributeSet attrs) { super(context, attrs); initAttrs(context, attrs); initViews(); } public ContentLoaderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttrs(context, attrs); initViews(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ContentLoaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } private void initAttrs(Context ctx, AttributeSet attr){ TypedArray ta = ctx.obtainStyledAttributes(attr, R.styleable.ContentLoaderView); padding = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_padding, -1); paddingLeft = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingLeft, -1); paddingRight = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingRight, -1); paddingTop = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingTop, -1); paddingBottom = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingBottom, -1); clipToPadding = ta.getBoolean(R.styleable.ContentLoaderView_android_clipToPadding, true); ta.recycle(); } private void initViews(){ inflate(getContext(), R.layout.merge_content_loader, this); setPadding(0, 0, 0, 0); setClickable(true); errorView = findViewById(R.id.error_view); emptyView = findViewById(R.id.empty_view); loadingView = findViewById(R.id.loading_view); refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresher); recyclerView = (RecyclerView) findViewById(R.id.recycler); errorRetryBtn = (Button) findViewById(R.id.btn_error_retry); errorMessageTV = (TextView) findViewById(R.id.error_message); emptyRetryBtn = (Button) findViewById(R.id.btn_empty_retry); emptyMessageTV = (TextView) findViewById(R.id.empty_message); errorRetryBtn.setOnClickListener(this); emptyRetryBtn.setOnClickListener(this); contentView = refreshLayout; refreshLayout.setEnabled(false); refreshLayout.setColorSchemeResources( android.R.color.holo_blue_light, android.R.color.holo_purple, android.R.color.holo_green_light, android.R.color.holo_red_light, android.R.color.holo_orange_light); refreshLayout.setOnRefreshListener(this); recyclerView.setOnScrollListener(mRecyclerScrollListener); if(padding!=-1){ recyclerView.setPadding(padding, padding, padding,padding); }else{ recyclerView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } recyclerView.setClipToPadding(clipToPadding); setDisplayState(STATE_LOADING); } RecyclerView.OnScrollListener mRecyclerScrollListener = new RecyclerView.OnScrollListener() { int totalItemCount; int visibleItemCount; int firstVisibleItemPosition; @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); RecyclerView.LayoutManager layoutManager; layoutManager = recyclerView.getLayoutManager(); totalItemCount = layoutManager.getItemCount(); visibleItemCount = layoutManager.getChildCount(); View firstVisibleChild = recyclerView.getChildAt(0); firstVisibleItemPosition = recyclerView.getChildPosition(firstVisibleChild); if(totalPage > currentPage && !loadMore&& (firstVisibleItemPosition+visibleItemCount+LOAD_MORE_ITEM_SLOP) >= totalItemCount ){ loadMore = true; if(moreListener !=null){ moreListener.onMore(++currentPage); refreshLayout.setEnabled(false); Timber.d("load more page:%d", currentPage); } } } }; public void setAdapter(RecyclerView.Adapter adapter){ recyclerView.setAdapter(adapter); if(adapter.getItemCount()> 0){ setDisplayState(STATE_CONTENT); } adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); update(); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { super.onItemRangeChanged(positionStart, itemCount); update(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); update(); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { super.onItemRangeRemoved(positionStart, itemCount); update(); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { super.onItemRangeMoved(fromPosition, toPosition, itemCount); update(); } private void update(){ Timber.d("Adapter updated, itemCount:%d", recyclerView.getLayoutManager().getItemCount()); int itemCount = recyclerView.getLayoutManager().getItemCount(); if(itemCount>0){ if(loadMore){ loadMoreCompleted(); } setDisplayState(STATE_CONTENT); }else{ setDisplayState(STATE_EMPTY); } if(refreshLayout.isRefreshing()){ refreshLayout.setRefreshing(false); } } }); } // @OnClick(R.id.btn_retry) public void onRetryButtonClick(){ refreshLayout.setRefreshing(true); setDisplayState(STATE_CONTENT); if(refreshListener !=null){ refreshListener.onRefresh(false); } } public void notifyLoadFailed(Throwable error){ if(refreshLayout.isRefreshing()){ refreshLayout.setRefreshing(false); } loadMore = false; if(currentPage==1 && recyclerView.getLayoutManager().getChildCount() == 0){ errorMessageTV.setText(error.getMessage()); setDisplayState(STATE_ERROR); }else{ setDisplayState(STATE_CONTENT); Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); } } private void loadMoreCompleted(){ loadMore = false; refreshLayout.setEnabled(true); setDisplayState(STATE_CONTENT); } public void setMoreListener(OnMoreListener moreListener) { this.moreListener = moreListener; } public void setPage(int currentPage, int totalPage) { this.currentPage = currentPage; this.totalPage = totalPage; } private void setDisplayState(int state){ this.displayState = state; loadingView.setVisibility(state==STATE_LOADING ? VISIBLE : GONE); emptyView.setVisibility(state==STATE_EMPTY ? VISIBLE : GONE); errorView.setVisibility(state==STATE_ERROR ? VISIBLE : GONE); contentView.setVisibility(state == STATE_CONTENT ? VISIBLE : GONE); } public void setOnRefreshListener(OnRefreshListener refreshListener) { this.refreshListener = refreshListener; refreshLayout.setEnabled(true); } @Override public void onRefresh() { currentPage = 1; if(refreshListener !=null){ refreshListener.onRefresh(true); } } @Override protected Parcelable onSaveInstanceState() { Parcelable parcelable = super.onSaveInstanceState(); SavedState savedState = new SavedState(parcelable); savedState.state = this.displayState; return savedState; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); this.displayState = savedState.state; setDisplayState(this.displayState); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_error_retry: case R.id.btn_empty_retry:{ setDisplayState(STATE_LOADING); onRefresh(); break; } } } public static interface OnRefreshListener { void onRefresh(boolean fromSwipe); } public static interface OnMoreListener { void onMore(int page); } static class SavedState extends BaseSavedState{ private int state; SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); try { state = source.readInt(); } catch (IllegalArgumentException e) { state = STATE_LOADING; } } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(state); } public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }