/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.saikali.android_skwissh.widgets.pulltorefresh;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.FrameLayout;
import android.widget.ListAdapter;
import com.saikali.android_skwissh.R;
import com.saikali.android_skwissh.widgets.pulltorefresh.internal.EmptyViewMethodAccessor;
import com.saikali.android_skwissh.widgets.pulltorefresh.internal.IndicatorLayout;
public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements OnScrollListener {
static final boolean DEFAULT_SHOW_INDICATOR = true;
private int mSavedLastVisibleIndex = -1;
private OnScrollListener mOnScrollListener;
private OnLastItemVisibleListener mOnLastItemVisibleListener;
private View mEmptyView;
private IndicatorLayout mIndicatorIvTop;
private IndicatorLayout mIndicatorIvBottom;
private boolean mShowIndicator;
private boolean mScrollEmptyView = true;
public PullToRefreshAdapterViewBase(Context context) {
super(context);
this.mRefreshableView.setOnScrollListener(this);
}
public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
super(context, attrs);
this.mRefreshableView.setOnScrollListener(this);
}
public PullToRefreshAdapterViewBase(Context context, Mode mode) {
super(context, mode);
this.mRefreshableView.setOnScrollListener(this);
}
@Override
abstract public ContextMenuInfo getContextMenuInfo();
/**
* Gets whether an indicator graphic should be displayed when the View is in
* a state where a Pull-to-Refresh can happen. An example of this state is
* when the Adapter View is scrolled to the top and the mode is set to
* {@link Mode#PULL_DOWN_TO_REFRESH}. The default value is
* {@value #DEFAULT_SHOW_INDICATOR}.
*
* @return true if the indicators will be shown
*/
public boolean getShowIndicator() {
return this.mShowIndicator;
}
@Override
public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
if (DEBUG) {
Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount + ". Total Items: " + totalItemCount);
}
// If we have a OnItemVisibleListener, do check...
if (null != this.mOnLastItemVisibleListener) {
// Detect whether the last visible item has changed
final int lastVisibleItemIndex = firstVisibleItem + visibleItemCount;
/**
* Check that the last item has changed, we have any items, and that
* the last item is visible. lastVisibleItemIndex is a zero-based
* index, so we add one to it to check against totalItemCount.
*/
if (visibleItemCount > 0 && (lastVisibleItemIndex + 1) == totalItemCount) {
if (lastVisibleItemIndex != this.mSavedLastVisibleIndex) {
this.mSavedLastVisibleIndex = lastVisibleItemIndex;
this.mOnLastItemVisibleListener.onLastItemVisible();
}
}
}
// If we're showing the indicator, check positions...
if (this.getShowIndicatorInternal()) {
this.updateIndicatorViewsVisibility();
}
// Finally call OnScrollListener if we have one
if (null != this.mOnScrollListener) {
this.mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
@Override
public final void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (null != this.mOnScrollListener) {
this.mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != this.mEmptyView && !this.mScrollEmptyView) {
this.mEmptyView.scrollTo(-l, -t);
}
}
/**
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
* getRefreshableView()}.{@link AdapterView#setAdapter(ListAdapter)
* setAdapter(adapter)}. This is just for convenience!
*
* @param adapter
* - Adapter to set
*/
public void setAdapter(ListAdapter adapter) {
((AdapterView<ListAdapter>) this.mRefreshableView).setAdapter(adapter);
}
/**
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
* getRefreshableView()}.
* {@link AdapterView#setOnItemClickListener(OnItemClickListener)
* setOnItemClickListener(listener)}. This is just for convenience!
*
* @param listener
* - OnItemClickListener to use
*/
public void setOnItemClickListener(OnItemClickListener listener) {
this.mRefreshableView.setOnItemClickListener(listener);
}
/**
* Sets the Empty View to be used by the Adapter View.
*
* We need it handle it ourselves so that we can Pull-to-Refresh when the
* Empty View is shown.
*
* Please note, you do <strong>not</strong> usually need to call this method
* yourself. Calling setEmptyView on the AdapterView will automatically call
* this method and set everything up. This includes when the Android
* Framework automatically sets the Empty View based on it's ID.
*
* @param newEmptyView
* - Empty View to be used
*/
public final void setEmptyView(View newEmptyView) {
FrameLayout refreshableViewWrapper = this.getRefreshableViewWrapper();
// If we already have an Empty View, remove it
if (null != this.mEmptyView) {
refreshableViewWrapper.removeView(this.mEmptyView);
}
if (null != newEmptyView) {
// New view needs to be clickable so that Android recognizes it as a
// target for Touch Events
newEmptyView.setClickable(true);
ViewParent newEmptyViewParent = newEmptyView.getParent();
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
}
refreshableViewWrapper.addView(newEmptyView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
if (this.mRefreshableView instanceof EmptyViewMethodAccessor) {
((EmptyViewMethodAccessor) this.mRefreshableView).setEmptyViewInternal(newEmptyView);
} else {
this.mRefreshableView.setEmptyView(newEmptyView);
}
this.mEmptyView = newEmptyView;
}
}
public final void setScrollEmptyView(boolean doScroll) {
this.mScrollEmptyView = doScroll;
}
public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
this.mOnLastItemVisibleListener = listener;
}
public final void setOnScrollListener(OnScrollListener listener) {
this.mOnScrollListener = listener;
};
/**
* Sets whether an indicator graphic should be displayed when the View is in
* a state where a Pull-to-Refresh can happen. An example of this state is
* when the Adapter View is scrolled to the top and the mode is set to
* {@link Mode#PULL_DOWN_TO_REFRESH}
*
* @param showIndicator
* - true if the indicators should be shown.
*/
public void setShowIndicator(boolean showIndicator) {
this.mShowIndicator = showIndicator;
if (this.getShowIndicatorInternal()) {
// If we're set to Show Indicator, add/update them
this.addIndicatorViews();
} else {
// If not, then remove then
this.removeIndicatorViews();
}
}
@Override
protected void handleStyledAttributes(TypedArray a) {
// Set Show Indicator to the XML value, or default value
this.mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, DEFAULT_SHOW_INDICATOR);
}
@Override
protected boolean isReadyForPullDown() {
return this.isFirstItemVisible();
}
@Override
protected boolean isReadyForPullUp() {
return this.isLastItemVisible();
}
@Override
protected void onPullToRefresh() {
super.onPullToRefresh();
if (this.getShowIndicatorInternal()) {
switch (this.getCurrentMode()) {
case PULL_UP_TO_REFRESH:
this.mIndicatorIvBottom.pullToRefresh();
break;
case PULL_DOWN_TO_REFRESH:
this.mIndicatorIvTop.pullToRefresh();
break;
}
}
}
@Override
protected void onReleaseToRefresh() {
super.onReleaseToRefresh();
if (this.getShowIndicatorInternal()) {
switch (this.getCurrentMode()) {
case PULL_UP_TO_REFRESH:
this.mIndicatorIvBottom.releaseToRefresh();
break;
case PULL_DOWN_TO_REFRESH:
this.mIndicatorIvTop.releaseToRefresh();
break;
}
}
}
@Override
protected void resetHeader() {
super.resetHeader();
if (this.getShowIndicatorInternal()) {
this.updateIndicatorViewsVisibility();
}
}
@Override
protected void setRefreshingInternal(boolean doScroll) {
super.setRefreshingInternal(doScroll);
if (this.getShowIndicatorInternal()) {
this.updateIndicatorViewsVisibility();
}
}
@Override
protected void updateUIForMode() {
super.updateUIForMode();
// Check Indicator Views consistent with new Mode
if (this.getShowIndicatorInternal()) {
this.addIndicatorViews();
} else {
this.removeIndicatorViews();
}
}
private void addIndicatorViews() {
Mode mode = this.getMode();
FrameLayout refreshableViewWrapper = this.getRefreshableViewWrapper();
if (mode.canPullDown() && null == this.mIndicatorIvTop) {
// If the mode can pull down, and we don't have one set already
this.mIndicatorIvTop = new IndicatorLayout(this.getContext(), Mode.PULL_DOWN_TO_REFRESH);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.rightMargin = this.getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
params.gravity = Gravity.TOP | Gravity.RIGHT;
refreshableViewWrapper.addView(this.mIndicatorIvTop, params);
} else if (!mode.canPullDown() && null != this.mIndicatorIvTop) {
// If we can't pull down, but have a View then remove it
refreshableViewWrapper.removeView(this.mIndicatorIvTop);
this.mIndicatorIvTop = null;
}
if (mode.canPullUp() && null == this.mIndicatorIvBottom) {
// If the mode can pull down, and we don't have one set already
this.mIndicatorIvBottom = new IndicatorLayout(this.getContext(), Mode.PULL_UP_TO_REFRESH);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.rightMargin = this.getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
refreshableViewWrapper.addView(this.mIndicatorIvBottom, params);
} else if (!mode.canPullUp() && null != this.mIndicatorIvBottom) {
// If we can't pull down, but have a View then remove it
refreshableViewWrapper.removeView(this.mIndicatorIvBottom);
this.mIndicatorIvBottom = null;
}
}
private boolean getShowIndicatorInternal() {
return this.mShowIndicator && this.isPullToRefreshEnabled();
}
private boolean isFirstItemVisible() {
final Adapter adapter = this.mRefreshableView.getAdapter();
if (null == adapter || adapter.isEmpty()) {
if (DEBUG) {
Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
}
return true;
} else {
/**
* This check should really just be:
* mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
* internally use a HeaderView which messes the positions up. For
* now we'll just add one to account for it and rely on the inner
* condition which checks getTop().
*/
if (this.mRefreshableView.getFirstVisiblePosition() <= 1) {
final View firstVisibleChild = this.mRefreshableView.getChildAt(0);
if (firstVisibleChild != null)
return firstVisibleChild.getTop() >= this.mRefreshableView.getTop();
}
}
return false;
}
private boolean isLastItemVisible() {
final Adapter adapter = this.mRefreshableView.getAdapter();
if (null == adapter || adapter.isEmpty()) {
if (DEBUG) {
Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
}
return true;
} else {
final int lastItemPosition = this.mRefreshableView.getCount() - 1;
final int lastVisiblePosition = this.mRefreshableView.getLastVisiblePosition();
if (DEBUG) {
Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: " + lastVisiblePosition);
}
/**
* This check should really just be: lastVisiblePosition ==
* lastItemPosition, but PtRListView internally uses a FooterView
* which messes the positions up. For me we'll just subtract one to
* account for it and rely on the inner condition which checks
* getBottom().
*/
if (lastVisiblePosition >= lastItemPosition - 1) {
final int childIndex = lastVisiblePosition - this.mRefreshableView.getFirstVisiblePosition();
final View lastVisibleChild = this.mRefreshableView.getChildAt(childIndex);
if (lastVisibleChild != null)
return lastVisibleChild.getBottom() <= this.mRefreshableView.getBottom();
}
}
return false;
}
private void removeIndicatorViews() {
if (null != this.mIndicatorIvTop) {
this.getRefreshableViewWrapper().removeView(this.mIndicatorIvTop);
this.mIndicatorIvTop = null;
}
if (null != this.mIndicatorIvBottom) {
this.getRefreshableViewWrapper().removeView(this.mIndicatorIvBottom);
this.mIndicatorIvBottom = null;
}
}
private void updateIndicatorViewsVisibility() {
if (null != this.mIndicatorIvTop) {
if (!this.isRefreshing() && this.isReadyForPullDown()) {
if (!this.mIndicatorIvTop.isVisible()) {
this.mIndicatorIvTop.show();
}
} else {
if (this.mIndicatorIvTop.isVisible()) {
this.mIndicatorIvTop.hide();
}
}
}
if (null != this.mIndicatorIvBottom) {
if (!this.isRefreshing() && this.isReadyForPullUp()) {
if (!this.mIndicatorIvBottom.isVisible()) {
this.mIndicatorIvBottom.show();
}
} else {
if (this.mIndicatorIvBottom.isVisible()) {
this.mIndicatorIvBottom.hide();
}
}
}
}
}