/*
* Copyright 2013 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 uk.co.senab.actionbarpulltorefresh.library;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.os.Build;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.RelativeLayout;
import android.widget.TextView;
import fr.castorflex.android.smoothprogressbar.SmoothProgressBar;
import fr.castorflex.android.smoothprogressbar.SmoothProgressDrawable;
import uk.co.senab.actionbarpulltorefresh.library.sdk.Compat;
/**
* Default Header Transformer.
*/
public class DefaultHeaderTransformer extends HeaderTransformer {
public static final int PROGRESS_BAR_STYLE_INSIDE = 0;
public static final int PROGRESS_BAR_STYLE_OUTSIDE = 1;
private View mHeaderView;
private ViewGroup mContentLayout;
private TextView mHeaderTextView;
private SmoothProgressBar mHeaderProgressBar;
private CharSequence mPullRefreshLabel, mRefreshingLabel, mReleaseLabel;
private int mProgressDrawableColor;
private long mAnimationDuration;
private int mProgressBarStyle;
private int mProgressBarHeight = RelativeLayout.LayoutParams.WRAP_CONTENT;
private final Interpolator mInterpolator = new AccelerateInterpolator();
protected DefaultHeaderTransformer() {
final int min = getMinimumApiLevel();
if (Build.VERSION.SDK_INT < min) {
throw new IllegalStateException("This HeaderTransformer is designed to run on SDK "
+ min
+ "+. If using ActionBarSherlock or ActionBarCompat you should use the appropriate provided extra.");
}
}
@Override
public void onViewCreated(Activity activity, View headerView) {
mHeaderView = headerView;
// Get ProgressBar and TextView
mHeaderProgressBar = (SmoothProgressBar) headerView.findViewById(R.id.ptr_progress);
mHeaderTextView = (TextView) headerView.findViewById(R.id.ptr_text);
mContentLayout = (ViewGroup) headerView.findViewById(R.id.ptr_content);
// Default Labels to display
mPullRefreshLabel = activity.getString(R.string.pull_to_refresh_pull_label);
mRefreshingLabel = activity.getString(R.string.pull_to_refresh_refreshing_label);
mReleaseLabel = activity.getString(R.string.pull_to_refresh_release_label);
mAnimationDuration = activity.getResources()
.getInteger(android.R.integer.config_shortAnimTime);
mProgressDrawableColor = activity.getResources()
.getColor(R.color.default_progress_bar_color);
// Setup the View styles
setupViewsFromStyles(activity, headerView);
applyProgressBarStyle();
// Apply any custom ProgressBar colors and corner radius
applyProgressBarSettings();
// FIXME: I do not like this call here
onReset();
}
@Override
public void onConfigurationChanged(Activity activity, Configuration newConfig) {
setupViewsFromStyles(activity, getHeaderView());
}
@Override
public void onReset() {
// Reset Progress Bar
if (mHeaderProgressBar != null) {
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderProgressBar.setProgress(0);
mHeaderProgressBar.setIndeterminate(false);
}
// Reset Text View
if (mHeaderTextView != null) {
mHeaderTextView.setVisibility(View.VISIBLE);
mHeaderTextView.setText(mPullRefreshLabel);
}
// Reset the Content Layout
if (mContentLayout != null) {
mContentLayout.setVisibility(View.VISIBLE);
Compat.setAlpha(mContentLayout, 1f);
}
}
@Override
public void onPulled(float percentagePulled) {
if (mHeaderProgressBar != null) {
mHeaderProgressBar.setVisibility(View.VISIBLE);
final float progress = mInterpolator.getInterpolation(percentagePulled);
mHeaderProgressBar.setProgress(Math.round(mHeaderProgressBar.getMax() * progress));
}
}
@Override
public void onRefreshStarted() {
if (mHeaderTextView != null) {
mHeaderTextView.setText(mRefreshingLabel);
}
if (mHeaderProgressBar != null) {
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderProgressBar.setIndeterminate(true);
}
}
@Override
public void onReleaseToRefresh() {
if (mHeaderTextView != null) {
mHeaderTextView.setText(mReleaseLabel);
}
if (mHeaderProgressBar != null) {
mHeaderProgressBar.setProgress(mHeaderProgressBar.getMax());
}
}
@Override
public void onRefreshMinimized() {
// Here we fade out most of the header, leaving just the progress bar
if (mContentLayout != null) {
ObjectAnimator.ofFloat(mContentLayout, "alpha", 1f, 0f).start();
}
}
public View getHeaderView() {
return mHeaderView;
}
@Override
public boolean showHeaderView() {
final boolean changeVis = mHeaderView.getVisibility() != View.VISIBLE;
if (changeVis) {
mHeaderView.setVisibility(View.VISIBLE);
AnimatorSet animSet = new AnimatorSet();
ObjectAnimator transAnim = ObjectAnimator.ofFloat(mContentLayout, "translationY",
-mContentLayout.getHeight(), 0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mHeaderView, "alpha", 0f, 1f);
animSet.playTogether(transAnim, alphaAnim);
animSet.setDuration(mAnimationDuration);
animSet.start();
}
return changeVis;
}
@Override
public boolean hideHeaderView() {
final boolean changeVis = mHeaderView.getVisibility() != View.GONE;
if (changeVis) {
Animator animator;
if (mContentLayout.getAlpha() >= 0.5f) {
// If the content layout is showing, translate and fade out
animator = new AnimatorSet();
ObjectAnimator transAnim = ObjectAnimator.ofFloat(mContentLayout, "translationY",
0f, -mContentLayout.getHeight());
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mHeaderView, "alpha", 1f, 0f);
((AnimatorSet) animator).playTogether(transAnim, alphaAnim);
} else {
// If the content layout isn't showing (minimized), just fade out
animator = ObjectAnimator.ofFloat(mHeaderView, "alpha", 1f, 0f);
}
animator.setDuration(mAnimationDuration);
animator.addListener(new HideAnimationCallback());
animator.start();
}
return changeVis;
}
/**
* Set color to apply to the progress bar.
* <p/>
* The best way to apply a color is to load the color from resources: {@code
* setProgressBarColor(getResources().getColor(R.color.your_color_name))}.
*
* @param color The color to use.
*/
public void setProgressBarColor(int color) {
if (color != mProgressDrawableColor) {
mProgressDrawableColor = color;
applyProgressBarSettings();
}
}
/**
* Set the progress bar style. {@code style} must be one of {@link #PROGRESS_BAR_STYLE_OUTSIDE}
* or {@link #PROGRESS_BAR_STYLE_INSIDE}.
*/
public void setProgressBarStyle(int style) {
if (mProgressBarStyle != style) {
mProgressBarStyle = style;
applyProgressBarStyle();
}
}
/**
* Set the progress bar height.
*/
public void setProgressBarHeight(int height) {
if (mProgressBarHeight != height) {
mProgressBarHeight = height;
applyProgressBarStyle();
}
}
/**
* Set Text to show to prompt the user is pull (or keep pulling).
*
* @param pullText - Text to display.
*/
public void setPullText(CharSequence pullText) {
mPullRefreshLabel = pullText;
if (mHeaderTextView != null) {
mHeaderTextView.setText(mPullRefreshLabel);
}
}
/**
* Set Text to show to tell the user that a refresh is currently in progress.
*
* @param refreshingText - Text to display.
*/
public void setRefreshingText(CharSequence refreshingText) {
mRefreshingLabel = refreshingText;
}
/**
* Set Text to show to tell the user has scrolled enough to refresh.
*
* @param releaseText - Text to display.
*/
public void setReleaseText(CharSequence releaseText) {
mReleaseLabel = releaseText;
}
private void setupViewsFromStyles(Activity activity, View headerView) {
final TypedArray styleAttrs = obtainStyledAttrsFromThemeAttr(activity,
R.attr.ptrHeaderStyle, R.styleable.PullToRefreshHeader);
// Retrieve the Action Bar size from the app theme or the Action Bar's style
if (mContentLayout != null) {
final int height = styleAttrs.getDimensionPixelSize(
R.styleable.PullToRefreshHeader_ptrHeaderHeight, getActionBarSize(activity));
mContentLayout.getLayoutParams().height = height;
mContentLayout.requestLayout();
}
// Retrieve the Action Bar background from the app theme or the Action Bar's style (see #93)
Drawable bg = styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrHeaderBackground)
? styleAttrs.getDrawable(R.styleable.PullToRefreshHeader_ptrHeaderBackground)
: getActionBarBackground(activity);
if (bg != null) {
mHeaderTextView.setBackgroundDrawable(bg);
// If we have an opaque background we can remove the background from the content layout
if (mContentLayout != null && bg.getOpacity() == PixelFormat.OPAQUE) {
mContentLayout.setBackgroundResource(0);
}
}
// Retrieve the Action Bar Title Style from the app theme or the Action Bar's style
Context abContext = headerView.getContext();
final int titleTextStyle = styleAttrs
.getResourceId(R.styleable.PullToRefreshHeader_ptrHeaderTitleTextAppearance,
getActionBarTitleStyle(abContext));
if (titleTextStyle != 0) {
mHeaderTextView.setTextAppearance(abContext, titleTextStyle);
}
// Retrieve the Progress Bar Color the style
if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrProgressBarColor)) {
mProgressDrawableColor = styleAttrs.getColor(
R.styleable.PullToRefreshHeader_ptrProgressBarColor, mProgressDrawableColor);
}
mProgressBarStyle = styleAttrs.getInt(
R.styleable.PullToRefreshHeader_ptrProgressBarStyle, PROGRESS_BAR_STYLE_OUTSIDE);
if(styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrProgressBarHeight)) {
mProgressBarHeight = styleAttrs.getDimensionPixelSize(
R.styleable.PullToRefreshHeader_ptrProgressBarHeight, mProgressBarHeight);
}
// Retrieve the text strings from the style (if they're set)
if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrPullText)) {
mPullRefreshLabel = styleAttrs.getString(R.styleable.PullToRefreshHeader_ptrPullText);
}
if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrRefreshingText)) {
mRefreshingLabel = styleAttrs
.getString(R.styleable.PullToRefreshHeader_ptrRefreshingText);
}
if (styleAttrs.hasValue(R.styleable.PullToRefreshHeader_ptrReleaseText)) {
mReleaseLabel = styleAttrs.getString(R.styleable.PullToRefreshHeader_ptrReleaseText);
}
styleAttrs.recycle();
}
private void applyProgressBarStyle() {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, mProgressBarHeight);
switch (mProgressBarStyle) {
case PROGRESS_BAR_STYLE_INSIDE:
lp.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.ptr_content);
break;
case PROGRESS_BAR_STYLE_OUTSIDE:
lp.addRule(RelativeLayout.BELOW, R.id.ptr_content);
break;
}
if (mHeaderProgressBar != null)
mHeaderProgressBar.setLayoutParams(lp);
}
private void applyProgressBarSettings() {
if (mHeaderProgressBar != null) {
final int strokeWidth = mHeaderProgressBar.getResources()
.getDimensionPixelSize(R.dimen.ptr_progress_bar_stroke_width);
mHeaderProgressBar.setIndeterminateDrawable(
new SmoothProgressDrawable.Builder(mHeaderProgressBar.getContext())
.color(mProgressDrawableColor)
.width(strokeWidth)
.build());
ShapeDrawable shape = new ShapeDrawable();
shape.setShape(new RectShape());
shape.getPaint().setColor(mProgressDrawableColor);
ClipDrawable clipDrawable = new ClipDrawable(shape, Gravity.CENTER, ClipDrawable.HORIZONTAL);
mHeaderProgressBar.setProgressDrawable(clipDrawable);
}
}
protected Drawable getActionBarBackground(Context context) {
int[] android_styleable_ActionBar = {android.R.attr.background};
// Now get the action bar style values...
TypedArray abStyle = obtainStyledAttrsFromThemeAttr(context, android.R.attr.actionBarStyle,
android_styleable_ActionBar);
try {
// background is the first attr in the array above so it's index is 0.
return abStyle.getDrawable(0);
} finally {
abStyle.recycle();
}
}
protected int getActionBarSize(Context context) {
int[] attrs = {android.R.attr.actionBarSize};
TypedArray values = context.getTheme().obtainStyledAttributes(attrs);
try {
return values.getDimensionPixelSize(0, 0);
} finally {
values.recycle();
}
}
protected int getActionBarTitleStyle(Context context) {
int[] android_styleable_ActionBar = {android.R.attr.titleTextStyle};
// Now get the action bar style values...
TypedArray abStyle = obtainStyledAttrsFromThemeAttr(context, android.R.attr.actionBarStyle,
android_styleable_ActionBar);
try {
// titleTextStyle is the first attr in the array above so it's index is 0.
return abStyle.getResourceId(0, 0);
} finally {
abStyle.recycle();
}
}
protected int getMinimumApiLevel() {
return Build.VERSION_CODES.ICE_CREAM_SANDWICH;
}
class HideAnimationCallback extends AnimatorListenerAdapter {
@Override
public void onAnimationEnd(Animator animation) {
View headerView = getHeaderView();
if (headerView != null) {
headerView.setVisibility(View.GONE);
}
onReset();
}
}
protected static TypedArray obtainStyledAttrsFromThemeAttr(Context context, int themeAttr,
int[] styleAttrs) {
// Need to get resource id of style pointed to from the theme attr
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(themeAttr, outValue, true);
final int styleResId = outValue.resourceId;
// Now return the values (from styleAttrs) from the style
return context.obtainStyledAttributes(styleResId, styleAttrs);
}
}