/* * Copyright (C) 2015 Hippo Seven * * 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 * 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.hippo.ehviewer.widget; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.hippo.effect.ViewTransition; import com.hippo.ehviewer.R; import com.hippo.util.AssertUtils; import com.hippo.util.IntIdGenerator; import com.hippo.util.LayoutManagerUtils; import com.hippo.util.UiUtils; import com.hippo.widget.Snackbar; import com.hippo.widget.recyclerview.EasyRecyclerView; import com.hippo.widget.refreshlayout.RefreshLayout; import java.util.ArrayList; import java.util.List; public class ContentLayout extends FrameLayout { private ProgressBar mProgressBar; private ViewGroup mTipView; private RefreshLayout mRefreshLayout; private EasyRecyclerView mRecyclerView; private View mImageView; private TextView mTextView; private Snackbar mSnackbar; private ContentHelper mContentHelper; private int mRecyclerViewOriginTop; private int mRecyclerViewOriginBottom; private int mSnackbarOriginBottom; public ContentLayout(Context context) { super(context); init(context); } public ContentLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public ContentLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { LayoutInflater.from(context).inflate(R.layout.widget_content_layout, this); mProgressBar = (ProgressBar) getChildAt(0); mTipView = (ViewGroup) getChildAt(1); mRefreshLayout = (RefreshLayout) getChildAt(2); mSnackbar = (Snackbar) getChildAt(3); mRecyclerView = (EasyRecyclerView) mRefreshLayout.getChildAt(1); mImageView = mTipView.getChildAt(0); mTextView = (TextView) mTipView.getChildAt(1); // mRecyclerViewOriginTop = mRecyclerView.getPaddingTop(); mRecyclerViewOriginBottom = mRecyclerView.getPaddingBottom(); // Snackbar mSnackbarOriginBottom = mSnackbar.getPaddingBottom(); } public EasyRecyclerView getRecyclerView() { return mRecyclerView; } public void setHelper(ContentHelper helper) { mContentHelper = helper; helper.init(this); } public void setFitPaddingTop(int fitPaddingTop) { // RecyclerView mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), mRecyclerViewOriginTop + fitPaddingTop, mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingBottom()); // RefreshLayout mRefreshLayout.setProgressViewOffset(false, fitPaddingTop, fitPaddingTop + UiUtils.dp2pix(getContext(), 32)); // TODO } public void setFitPaddingBottom(int fitPaddingBottom) { // RecyclerView mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), mRecyclerView.getPaddingRight(), mRecyclerViewOriginBottom + fitPaddingBottom); // Snackbar mSnackbar.setPadding(mSnackbar.getPaddingLeft(), mSnackbar.getPaddingTop(), mSnackbar.getPaddingRight(), mSnackbarOriginBottom + fitPaddingBottom); } @Override public Parcelable onSaveInstanceState() { return mContentHelper.onSaveInstanceState(super.onSaveInstanceState()); } @Override public void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(mContentHelper.onRestoreInstanceState(state)); } public abstract static class ContentHelper extends EasyRecyclerView.Adapter implements RefreshLayout.OnHeaderRefreshListener, RefreshLayout.OnFooterRefreshListener, View.OnClickListener { private static final String STATE_KEY_SUPER = "super"; private static final String STATE_KEY_SHOWN_VIEW = "shown_view"; private static final String STATE_KEY_RECYCLER_VIEW = "recycler_view"; private static final String STATE_KEY_TIP_MESSAGE = "tip_message"; public static final int TYPE_REFRESH = 0; public static final int TYPE_PRE_PAGE = 1; public static final int TYPE_PRE_PAGE_KEEP_POS = 2; public static final int TYPE_NEXT_PAGE = 3; public static final int TYPE_NEXT_PAGE_KEEP_POS = 4; public static final int TYPE_SOMEWHERE = 5; private Context mContext; private ProgressBar mProgressBar; private ViewGroup mTipView; private RefreshLayout mRefreshLayout; private EasyRecyclerView mRecyclerView; private View mImageView; private TextView mTextView; private Snackbar mSnackbar; private RecyclerView.LayoutManager mLayoutManager; private ViewTransition mViewTransition; /** * Store data */ private List mData; private IntIdGenerator mIdGenerator; /** * First shown page index */ private int mFirstPage; /** * Last shown page index + 1 */ private int mLastPage; /** * First index index of current page */ private int mFirstIndex; /** * Last index index of current page + 1 */ private int mLastIndex; /** * The size of page */ private int mPageSize; private int mCurrentPage; private int mPageVolume; // TODO save mCurrentTaskId mCurrentTaskType mCurrentTaskPage private int mCurrentTaskId; private int mCurrentTaskType; private int mCurrentTaskPage; private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState){ if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (mPageVolume <= 0) { return; } if (mLastIndex == 0) { mCurrentPage = 0; mFirstIndex = 0; mLastIndex = mPageVolume; } int firstVisiblePosition = LayoutManagerUtils.getFirstVisibleItemPostion(mLayoutManager); int lastVisiblePosition = LayoutManagerUtils.getLastVisibleItemPostion(mLayoutManager); int pageChanged = (firstVisiblePosition - mFirstIndex) / mPageVolume; if (pageChanged <= 0) { pageChanged = (lastVisiblePosition - mLastIndex + 1) / mPageVolume; } if (pageChanged != 0) { mCurrentPage = mCurrentPage + pageChanged; int offset = pageChanged * mPageVolume; mFirstIndex += offset; mLastIndex += offset; } } } }; public ContentHelper(Context context) { mContext = context; mData = new ArrayList<>(); mIdGenerator = IntIdGenerator.create(); } protected ContentHelper(Context context, List data) { this(context); mData.addAll(data); } private void init(ContentLayout contentLayout) { mProgressBar = contentLayout.mProgressBar; mTipView = contentLayout.mTipView; mRefreshLayout = contentLayout.mRefreshLayout; mSnackbar = contentLayout.mSnackbar; mRecyclerView = contentLayout.mRecyclerView; mImageView = contentLayout.mImageView; mTextView = contentLayout.mTextView; mLayoutManager = generateLayoutManager(); mViewTransition = new ViewTransition(mRefreshLayout, mProgressBar, mTipView); mViewTransition.showView(2, false); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(this); mRecyclerView.addOnScrollListener(mOnScrollListener); mRefreshLayout.setOnHeaderRefreshListener(this); mRefreshLayout.setOnFooterRefreshListener(this); mTipView.setOnClickListener(this); mSnackbar.setAction(mContext.getString(R.string.retry), this); } protected List getData() { return mData; } protected abstract RecyclerView.LayoutManager generateLayoutManager(); protected abstract void onScrollToPosition(); public void showContent() { mViewTransition.showView(0); } public void showProgressBar() { showProgressBar(true); } public void showProgressBar(boolean animation) { mViewTransition.showView(1, animation); } public void showText(CharSequence text) { mViewTransition.showView(2); mTextView.setText(text); } /** * @throws IndexOutOfBoundsException * if {@code location < 0 || location >= size()} */ public E getDataAt(int location) { return mData.get(location); } /** * Call {@link #onGetPageData(int, List)} when get data * * @param taskId task id * @param page the page to get */ protected abstract void getPageData(int taskId, int type, int page); public void setPageSize(int pageSize) { mPageSize = pageSize; } public int getPageSize() { return mPageSize; } public int getCurrentPage() { return mCurrentPage; } public void resetPageSize() { mPageSize = Integer.MAX_VALUE; } public void onGetPageData(int taskId, List data) { showContent(); int pageVolume = data.size(); mPageVolume = pageVolume; if (mCurrentTaskId == taskId) { switch (mCurrentTaskType) { case TYPE_REFRESH: mFirstPage = 0; mLastPage = 1; mCurrentPage = 0; mFirstIndex = 0; mLastIndex = pageVolume; mData.clear(); mData.addAll(data); notifyDataSetChanged(); mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, 0, 0); onScrollToPosition(); break; case TYPE_PRE_PAGE: case TYPE_PRE_PAGE_KEEP_POS: mData.addAll(0, data); notifyItemRangeInserted(0, pageVolume); mFirstPage--; if (mCurrentTaskType == TYPE_PRE_PAGE_KEEP_POS) { mFirstIndex += pageVolume; mLastIndex += pageVolume; // TODO } else { mCurrentPage = mFirstPage; mFirstIndex = 0; mLastIndex = pageVolume; mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, 0, 0); onScrollToPosition(); } break; case TYPE_NEXT_PAGE: case TYPE_NEXT_PAGE_KEEP_POS: int oldDataSize = mData.size(); mData.addAll(data); notifyItemRangeInserted(oldDataSize, pageVolume); mLastPage++; if (mCurrentTaskType != TYPE_NEXT_PAGE_KEEP_POS) { mCurrentPage = mLastPage - 1; mFirstIndex = mData.size() - pageVolume; mLastIndex = mData.size(); mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, mFirstIndex, 0); onScrollToPosition(); } break; case TYPE_SOMEWHERE: mData.clear(); mData.addAll(data); notifyDataSetChanged(); mFirstPage = mCurrentTaskPage; mLastPage = mCurrentTaskPage + 1; mCurrentPage = mCurrentTaskPage; mFirstIndex = 0; mLastIndex = pageVolume; mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, 0, 0); onScrollToPosition(); break; } } mRefreshLayout.setHeaderRefreshing(false); mRefreshLayout.setFooterRefreshing(false); } public void onGetPageData(int taskId, Exception e) { if (mCurrentTaskId == taskId) { mRefreshLayout.setHeaderRefreshing(false); mRefreshLayout.setFooterRefreshing(false); showText(e.getClass().getName()); } } @Override public boolean onFooterRefresh() { if (mLastPage >= mPageSize) { return false; } else { mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS; mCurrentTaskPage = mLastPage; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); return true; } } @Override public void onHeaderRefresh() { if (mFirstPage > 0) { mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_PRE_PAGE_KEEP_POS; mCurrentTaskPage = mFirstPage - 1; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); } else { doRefresh(); } } @Override public void onClick(View v) { if (v == mTipView) { refreshWithSameSearch(); } } private void doRefresh() { mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_REFRESH; mCurrentTaskPage = 0; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); } public void firstRefresh() { showProgressBar(false); doRefresh(); } /** * Show progress bar first, than do refresh */ public void refresh() { showProgressBar(); doRefresh(); } public void refreshWithSameSearch() { if (mViewTransition.getShownViewIndex() == 0) { // Go to top mRecyclerView.stopScroll(); mLayoutManager.smoothScrollToPosition(mRecyclerView, null, 0); // Show header refresh mRefreshLayout.setFooterRefreshing(false); mRefreshLayout.setHeaderRefreshing(true); // Do refresh doRefresh(); } else { showProgressBar(); doRefresh(); } } public void cancelCurrentTask() { mCurrentTaskId = mIdGenerator.nextId(); } public boolean canGoTo() { return mViewTransition.getShownViewIndex() == 0; } /** * Only work when data is loaded */ public void goTo(int page) { if (page < 0 || page >= mPageSize) { throw new IndexOutOfBoundsException("Page size is " + mPageSize + ", page is " + page); } else if (page >= mFirstPage && page < mLastPage) { cancelCurrentTask(); mCurrentPage = page; mFirstIndex = mPageVolume * (page - mFirstPage); mLastIndex = mFirstIndex + mPageVolume; int position = mFirstIndex; mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, position, 0); onScrollToPosition(); } else if (page == mFirstPage - 1) { mRefreshLayout.setFooterRefreshing(false); mRefreshLayout.setHeaderRefreshing(true); mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_PRE_PAGE; mCurrentTaskPage = page; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); } else if (page == mLastPage) { mRefreshLayout.setFooterRefreshing(false); mRefreshLayout.setHeaderRefreshing(true); mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_NEXT_PAGE; mCurrentTaskPage = page; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); } else { mRefreshLayout.setFooterRefreshing(false); mRefreshLayout.setHeaderRefreshing(true); mCurrentTaskId = mIdGenerator.nextId(); mCurrentTaskType = TYPE_SOMEWHERE; mCurrentTaskPage = page; getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage); } } /** * Reload data to layout. Maybe do it when change item view style */ public void reload() { notifyDataSetChanged(); mCurrentPage = mFirstPage; mFirstIndex = 0; mLastIndex = mPageVolume; mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionWithOffset(mLayoutManager, 0, 0); onScrollToPosition(); } @Override public int getItemCount() { return mData.size(); } protected Parcelable onSaveInstanceState(Parcelable superParcelable) { final Bundle state = new Bundle(); state.putParcelable(STATE_KEY_SUPER, superParcelable); state.putInt(STATE_KEY_SHOWN_VIEW, mViewTransition.getShownViewIndex()); state.putParcelable(STATE_KEY_RECYCLER_VIEW, mRecyclerView.onSaveInstanceState()); state.putString(STATE_KEY_TIP_MESSAGE, mTextView.getText().toString()); return state; } protected Parcelable onRestoreInstanceState(Parcelable state) { AssertUtils.assertInstanceof("state must be Bundle", state, Bundle.class); final Bundle savedState = (Bundle) state; mViewTransition.showView(savedState.getInt(STATE_KEY_SHOWN_VIEW), false); mRecyclerView.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_RECYCLER_VIEW)); mTextView.setText(savedState.getString(STATE_KEY_TIP_MESSAGE)); return savedState.getParcelable(STATE_KEY_SUPER); } } }