Java tutorial
/* * Copyright (c) linroid 2015. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.linroid.pushapp.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.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; import com.linroid.pushapp.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; public SwipeRefreshLayout refreshLayout; public 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); int errorViewId = ta.getResourceId(R.styleable.ContentLoaderView_errorView, R.layout.content_loader_error); int emptyViewId = ta.getResourceId(R.styleable.ContentLoaderView_emptyView, R.layout.content_loader_empty); int loadingViewId = ta.getResourceId(R.styleable.ContentLoaderView_loadingView, R.layout.content_loader_loading); int contentViewId = ta.getResourceId(R.styleable.ContentLoaderView_contentView, R.layout.content_loader_content); ta.recycle(); LayoutInflater inflater = LayoutInflater.from(ctx); errorView = inflater.inflate(errorViewId, this, false); emptyView = inflater.inflate(emptyViewId, this, false); loadingView = inflater.inflate(loadingViewId, this, false); contentView = inflater.inflate(contentViewId, this, false); addView(errorView); addView(emptyView); addView(loadingView); addView(contentView); } private void initViews() { setPadding(0, 0, 0, 0); setClickable(true); refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresher); recyclerView = (RecyclerView) findViewById(R.id.recycler); errorRetryBtn = (Button) errorView.findViewById(R.id.btn_error_retry); errorMessageTV = (TextView) errorView.findViewById(R.id.error_message); emptyRetryBtn = (Button) emptyView.findViewById(R.id.btn_empty_retry); emptyMessageTV = (TextView) emptyView.findViewById(R.id.empty_message); if (errorRetryBtn != null) { errorRetryBtn.setOnClickListener(this); } if (emptyRetryBtn != null) { 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.addOnScrollListener(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 interface OnRefreshListener { void onRefresh(boolean fromSwipe); } public 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]; } }; } }