/** * Copyright 2016 JustWayward Team * <p> * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.justwayward.reader.view.recyclerview; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.ColorRes; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.AdapterDataObserver; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.TextView; import com.justwayward.reader.R; import com.justwayward.reader.utils.NetworkUtils; import com.justwayward.reader.view.recyclerview.adapter.RecyclerArrayAdapter; import com.justwayward.reader.view.recyclerview.decoration.DividerDecoration; import com.justwayward.reader.view.recyclerview.swipe.OnRefreshListener; import com.justwayward.reader.view.recyclerview.swipe.SwipeRefreshLayout; import java.util.ArrayList; import java.util.List; public class EasyRecyclerView extends FrameLayout { private Context mContext; public static final String TAG = "EasyRecyclerView"; public static boolean DEBUG = false; protected RecyclerView mRecycler; protected TextView tipView; protected ViewGroup mProgressView; protected ViewGroup mEmptyView; protected ViewGroup mErrorView; private int mProgressId; private int mEmptyId; private int mErrorId; protected boolean mClipToPadding; protected int mPadding; protected int mPaddingTop; protected int mPaddingBottom; protected int mPaddingLeft; protected int mPaddingRight; protected int mScrollbarStyle; protected int mScrollbar; protected RecyclerView.OnScrollListener mInternalOnScrollListener; protected RecyclerView.OnScrollListener mExternalOnScrollListener; protected SwipeRefreshLayout mPtrLayout; protected OnRefreshListener mRefreshListener; public List<RecyclerView.ItemDecoration> decorations = new ArrayList<>(); public SwipeRefreshLayout getSwipeToRefresh() { return mPtrLayout; } public RecyclerView getRecyclerView() { return mRecycler; } public EasyRecyclerView(Context context) { this(context, null); } public EasyRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public EasyRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; if (attrs != null) initAttrs(attrs); initView(); } protected void initAttrs(AttributeSet attrs) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.superrecyclerview); try { mClipToPadding = a.getBoolean(R.styleable.superrecyclerview_recyclerClipToPadding, false); mPadding = (int) a.getDimension(R.styleable.superrecyclerview_recyclerPadding, -1.0f); mPaddingTop = (int) a.getDimension(R.styleable.superrecyclerview_recyclerPaddingTop, 0.0f); mPaddingBottom = (int) a.getDimension(R.styleable.superrecyclerview_recyclerPaddingBottom, 0.0f); mPaddingLeft = (int) a.getDimension(R.styleable.superrecyclerview_recyclerPaddingLeft, 0.0f); mPaddingRight = (int) a.getDimension(R.styleable.superrecyclerview_recyclerPaddingRight, 0.0f); mScrollbarStyle = a.getInteger(R.styleable.superrecyclerview_scrollbarStyle, -1); mScrollbar = a.getInteger(R.styleable.superrecyclerview_scrollbars, -1); mEmptyId = a.getResourceId(R.styleable.superrecyclerview_layout_empty, 0); mProgressId = a.getResourceId(R.styleable.superrecyclerview_layout_progress, 0); mErrorId = a.getResourceId(R.styleable.superrecyclerview_layout_error, R.layout.common_net_error_view); } finally { a.recycle(); } } private void initView() { if (isInEditMode()) { return; } //生成主View View v = LayoutInflater.from(getContext()).inflate(R.layout.common_recyclerview, this); mPtrLayout = (SwipeRefreshLayout) v.findViewById(R.id.ptr_layout); mPtrLayout.setEnabled(false); mProgressView = (ViewGroup) v.findViewById(R.id.progress); if (mProgressId != 0) LayoutInflater.from(getContext()).inflate(mProgressId, mProgressView); mEmptyView = (ViewGroup) v.findViewById(R.id.empty); if (mEmptyId != 0) LayoutInflater.from(getContext()).inflate(mEmptyId, mEmptyView); mErrorView = (ViewGroup) v.findViewById(R.id.error); if (mErrorId != 0) LayoutInflater.from(getContext()).inflate(mErrorId, mErrorView); initRecyclerView(v); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return mPtrLayout.dispatchTouchEvent(ev); } /** * @param left * @param top * @param right * @param bottom */ public void setRecyclerPadding(int left, int top, int right, int bottom) { this.mPaddingLeft = left; this.mPaddingTop = top; this.mPaddingRight = right; this.mPaddingBottom = bottom; mRecycler.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); } public void setClipToPadding(boolean isClip) { mRecycler.setClipToPadding(isClip); } public void setEmptyView(View emptyView) { mEmptyView.removeAllViews(); mEmptyView.addView(emptyView); } public void setProgressView(View progressView) { mProgressView.removeAllViews(); mProgressView.addView(progressView); } public void setErrorView(View errorView) { mErrorView.removeAllViews(); mErrorView.addView(errorView); } public void setEmptyView(int emptyView) { mEmptyView.removeAllViews(); LayoutInflater.from(getContext()).inflate(emptyView, mEmptyView); } public void setProgressView(int progressView) { mProgressView.removeAllViews(); LayoutInflater.from(getContext()).inflate(progressView, mProgressView); } public void setErrorView(int errorView) { mErrorView.removeAllViews(); LayoutInflater.from(getContext()).inflate(errorView, mErrorView); } public void scrollToPosition(int position) { getRecyclerView().scrollToPosition(position); } /** * Implement this method to customize the AbsListView */ protected void initRecyclerView(View view) { mRecycler = (RecyclerView) view.findViewById(android.R.id.list); tipView = (TextView) view.findViewById(R.id.tvTip); setItemAnimator(null); if (mRecycler != null) { mRecycler.setHasFixedSize(true); mRecycler.setClipToPadding(mClipToPadding); mInternalOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (mExternalOnScrollListener != null) mExternalOnScrollListener.onScrolled(recyclerView, dx, dy); } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (mExternalOnScrollListener != null) mExternalOnScrollListener.onScrollStateChanged(recyclerView, newState); } }; mRecycler.addOnScrollListener(mInternalOnScrollListener); if (mPadding != -1.0f) { mRecycler.setPadding(mPadding, mPadding, mPadding, mPadding); } else { mRecycler.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); } if (mScrollbarStyle != -1) { mRecycler.setScrollBarStyle(mScrollbarStyle); } switch (mScrollbar) { case 0: setVerticalScrollBarEnabled(false); break; case 1: setHorizontalScrollBarEnabled(false); break; case 2: setVerticalScrollBarEnabled(false); setHorizontalScrollBarEnabled(false); break; } } } @Override public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { mRecycler.setVerticalScrollBarEnabled(verticalScrollBarEnabled); } @Override public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) { mRecycler.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled); } /** * Set the layout manager to the recycler * * @param manager */ public void setLayoutManager(RecyclerView.LayoutManager manager) { mRecycler.setLayoutManager(manager); } /** * Set the ItemDecoration to the recycler * * @param color * @param height * @param paddingLeft * @param paddingRight */ public void setItemDecoration(int color, int height, int paddingLeft, int paddingRight) { DividerDecoration itemDecoration = new DividerDecoration(color, height, paddingLeft, paddingRight); itemDecoration.setDrawLastItem(false); decorations.add(itemDecoration); mRecycler.addItemDecoration(itemDecoration); } public static class EasyDataObserver extends AdapterDataObserver { private EasyRecyclerView recyclerView; public EasyDataObserver(EasyRecyclerView recyclerView) { this.recyclerView = recyclerView; } @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(); } @Override public void onChanged() { super.onChanged(); update(); } //自动更改Container的样式 private void update() { log("update"); int count; if (recyclerView.getAdapter() instanceof RecyclerArrayAdapter) { count = ((RecyclerArrayAdapter) recyclerView.getAdapter()).getCount(); } else { count = recyclerView.getAdapter().getItemCount(); } if (count == 0 && !NetworkUtils.isAvailable(recyclerView.getContext())) { recyclerView.showError(); return; } if (count == 0 && ((RecyclerArrayAdapter) recyclerView.getAdapter()).getHeaderCount() == 0) { log("no data:" + "show empty"); recyclerView.showEmpty(); } else { log("has data"); recyclerView.showRecycler(); } } } /** * 设置适配器,关闭所有副view。展示recyclerView * 适配器有更新,自动关闭所有副view。根据条数判断是否展示EmptyView * * @param adapter */ public void setAdapter(RecyclerView.Adapter adapter) { mRecycler.setAdapter(adapter); adapter.registerAdapterDataObserver(new EasyDataObserver(this)); showRecycler(); } /** * 设置适配器,关闭所有副view。展示进度条View * 适配器有更新,自动关闭所有副view。根据条数判断是否展示EmptyView * * @param adapter */ public void setAdapterWithProgress(RecyclerView.Adapter adapter) { mRecycler.setAdapter(adapter); adapter.registerAdapterDataObserver(new EasyDataObserver(this)); //只有Adapter为空时才显示ProgressView if (adapter instanceof RecyclerArrayAdapter) { if (((RecyclerArrayAdapter) adapter).getCount() == 0) { showProgress(); } else { showRecycler(); } } else { if (adapter.getItemCount() == 0) { showProgress(); } else { showRecycler(); } } } /** * Remove the adapter from the recycler */ public void clear() { mRecycler.setAdapter(null); } private void hideAll() { mEmptyView.setVisibility(View.GONE); mProgressView.setVisibility(View.GONE); mErrorView.setVisibility(GONE); // mPtrLayout.setRefreshing(false); mRecycler.setVisibility(View.INVISIBLE); } public void showError() { log("showError"); if (mErrorView.getChildCount() > 0) { hideAll(); mErrorView.setVisibility(View.VISIBLE); } else { showRecycler(); } } public void showEmpty() { log("showEmpty"); if (mEmptyView.getChildCount() > 0) { hideAll(); mEmptyView.setVisibility(View.VISIBLE); } else { showRecycler(); } } public void showProgress() { log("showProgress"); if (mProgressView.getChildCount() > 0) { hideAll(); mProgressView.setVisibility(View.VISIBLE); } else { showRecycler(); } } public void showRecycler() { log("showRecycler"); hideAll(); mRecycler.setVisibility(View.VISIBLE); } public void showTipViewAndDelayClose(String tip) { tipView.setText(tip); Animation mShowAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f); mShowAction.setDuration(500); tipView.startAnimation(mShowAction); tipView.setVisibility(View.VISIBLE); tipView.postDelayed(new Runnable() { @Override public void run() { Animation mHiddenAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f); mHiddenAction.setDuration(500); tipView.startAnimation(mHiddenAction); tipView.setVisibility(View.GONE); } }, 2200); } public void showTipView(String tip) { tipView.setText(tip); Animation mShowAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f); mShowAction.setDuration(500); tipView.startAnimation(mShowAction); tipView.setVisibility(View.VISIBLE); } public void hideTipView(long delayMillis) { tipView.postDelayed(new Runnable() { @Override public void run() { Animation mHiddenAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f); mHiddenAction.setDuration(500); tipView.startAnimation(mHiddenAction); tipView.setVisibility(View.GONE); } }, delayMillis); } public void setTipViewText(String tip) { if (!isTipViewVisible()) showTipView(tip); else tipView.setText(tip); } public boolean isTipViewVisible() { return tipView.getVisibility() == View.VISIBLE; } /** * Set the listener when refresh is triggered and enable the SwipeRefreshLayout * * @param listener */ public void setRefreshListener(OnRefreshListener listener) { mPtrLayout.setEnabled(true); mPtrLayout.setOnRefreshListener(listener); this.mRefreshListener = listener; } public void setRefreshing(final boolean isRefreshing) { mPtrLayout.post(new Runnable() { @Override public void run() { if (isRefreshing) { // 避免刷新的loadding和progressview 同时显示 mProgressView.setVisibility(View.GONE); } mPtrLayout.setRefreshing(isRefreshing); } }); } public void setRefreshing(final boolean isRefreshing, final boolean isCallbackListener) { mPtrLayout.post(new Runnable() { @Override public void run() { mPtrLayout.setRefreshing(isRefreshing); if (isRefreshing && isCallbackListener && mRefreshListener != null) { mRefreshListener.onRefresh(); } } }); } /** * Set the colors for the SwipeRefreshLayout states * * @param colRes */ public void setRefreshingColorResources(@ColorRes int... colRes) { mPtrLayout.setColorSchemeResources(colRes); } /** * Set the colors for the SwipeRefreshLayout states * * @param col */ public void setRefreshingColor(int... col) { mPtrLayout.setColorSchemeColors(col); } /** * Set the scroll listener for the recycler * * @param listener */ public void setOnScrollListener(RecyclerView.OnScrollListener listener) { mExternalOnScrollListener = listener; } /** * Add the onItemTouchListener for the recycler * * @param listener */ public void addOnItemTouchListener(RecyclerView.OnItemTouchListener listener) { mRecycler.addOnItemTouchListener(listener); } /** * Remove the onItemTouchListener for the recycler * * @param listener */ public void removeOnItemTouchListener(RecyclerView.OnItemTouchListener listener) { mRecycler.removeOnItemTouchListener(listener); } /** * @return the recycler adapter */ public RecyclerView.Adapter getAdapter() { return mRecycler.getAdapter(); } public void setOnTouchListener(OnTouchListener listener) { mRecycler.setOnTouchListener(listener); } public void setItemAnimator(RecyclerView.ItemAnimator animator) { mRecycler.setItemAnimator(animator); } public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration) { mRecycler.addItemDecoration(itemDecoration); } public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration, int index) { mRecycler.addItemDecoration(itemDecoration, index); } public void removeItemDecoration(RecyclerView.ItemDecoration itemDecoration) { mRecycler.removeItemDecoration(itemDecoration); } public void removeAllItemDecoration() { for (RecyclerView.ItemDecoration decoration : decorations) { mRecycler.removeItemDecoration(decoration); } } /** * @return inflated error view or null */ public View getErrorView() { if (mErrorView.getChildCount() > 0) return mErrorView.getChildAt(0); return null; } /** * @return inflated progress view or null */ public View getProgressView() { if (mProgressView.getChildCount() > 0) return mProgressView.getChildAt(0); return null; } /** * @return inflated empty view or null */ public View getEmptyView() { if (mEmptyView.getChildCount() > 0) return mEmptyView.getChildAt(0); return null; } private static void log(String content) { if (DEBUG) { Log.i(TAG, content); } } }