Skip to content

Instantly share code, notes, and snippets.

@erickogi
Last active July 26, 2019 13:31
Show Gist options
  • Save erickogi/7a04053a5b5e03eff38a9eab0ef00adc to your computer and use it in GitHub Desktop.
Save erickogi/7a04053a5b5e03eff38a9eab0ef00adc to your computer and use it in GitHub Desktop.
Slider codes
package com.kogicodes.sokoni.utils.slider.Transformers;
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class AccordionTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
ViewHelper.setPivotX(view, position < 0 ? 0 : view.getWidth());
ViewHelper.setScaleX(view, position < 0 ? 1f + position : 1f - position);
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class BackgroundToForegroundTransformer extends BaseTransformer {
private static final float min(float val, float min) {
return val < min ? min : val;
}
@Override
protected void onTransform(View view, float position) {
final float height = view.getHeight();
final float width = view.getWidth();
final float scale = min(position < 0 ? 1f : Math.abs(1f - position), 0.5f);
ViewHelper.setScaleX(view, scale);
ViewHelper.setScaleY(view, scale);
ViewHelper.setPivotX(view, width * 0.5f);
ViewHelper.setPivotY(view, height * 0.5f);
ViewHelper.setTranslationX(view, position < 0 ? width * position : -width * position * 0.25f);
}
}
package com.kogicodes.sokoni.utils.slider.Animations;
import android.view.View;
public interface BaseAnimationInterface {
/**
* When the current item prepare to start leaving the screen.
*
* @param current
*/
void onPrepareCurrentItemLeaveScreen(View current);
/**
* The next item which will be shown in ViewPager/
*
* @param next
*/
void onPrepareNextItemShowInScreen(View next);
/**
* Current item totally disappear from screen.
*
* @param view
*/
void onCurrentItemDisappear(View view);
/**
* Next item totally show in screen.
*
* @param view
*/
void onNextItemAppear(View view);
}
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.kogicodes.sokoni.R;
import com.kogicodes.sokoni.utils.MyImageRequestListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
/**
* When you want to make your own slider view, you must extends from this class.
* BaseSliderView provides some useful methods.
* I provide two example: {@link com.daimajia.slider.library.SliderTypes.DefaultSliderView} and
* {@link com.daimajia.slider.library.SliderTypes.TextSliderView}
* if you want to show progressbar, you just need to set a progressbar id as @+id/loading_bar.
*/
public abstract class BaseSliderView implements MyImageRequestListener.Callback {
protected Context mContext;
protected OnSliderClickListener mOnSliderClickListener;
private Bundle mBundle;
/**
* Error place holder logo_image.
*/
private int mErrorPlaceHolderRes;
private View v;
/**
* Empty imageView placeholder.
*/
private int mEmptyPlaceHolderRes;
private String mUrl;
private File mFile;
private int mRes;
private boolean mErrorDisappear;
private ImageLoadListener mLoadListener;
private String mDescription;
// private Picasso mPicasso;
/**
* Scale type of the logo_image.
*/
private ScaleType mScaleType = ScaleType.Fit;
protected BaseSliderView(Context context) {
mContext = context;
}
/**
* the placeholder logo_image when loading logo_image from url or file.
*
* @param resId Image resource id
* @return
*/
public BaseSliderView empty(int resId) {
mEmptyPlaceHolderRes = resId;
return this;
}
/**
* determine whether remove the logo_image which failed to download or load from file
*
* @param disappear
* @return
*/
public BaseSliderView errorDisappear(boolean disappear) {
mErrorDisappear = disappear;
return this;
}
/**
* if you set errorDisappear false, this will set a error placeholder logo_image.
*
* @param resId logo_image resource id
* @return
*/
public BaseSliderView error(int resId) {
mErrorPlaceHolderRes = resId;
return this;
}
/**
* the description of a slider logo_image.
*
* @param description
* @return
*/
public BaseSliderView description(String description) {
mDescription = description;
return this;
}
/**
* set a url as a logo_image that preparing to load
*
* @param url
* @return
*/
public BaseSliderView image(String url) {
if (mFile != null || mRes != 0) {
throw new IllegalStateException("Call multi logo_image function," +
"you only have permission to call it once");
}
mUrl = url;
return this;
}
/**
* set a file as a logo_image that will to load
*
* @param file
* @return
*/
public BaseSliderView image(File file) {
if (mUrl != null || mRes != 0) {
throw new IllegalStateException("Call multi logo_image function," +
"you only have permission to call it once");
}
mFile = file;
return this;
}
public BaseSliderView image(int res) {
if (mUrl != null || mFile != null) {
throw new IllegalStateException("Call multi logo_image function," +
"you only have permission to call it once");
}
mRes = res;
return this;
}
/**
* lets users add a bundle of additional information
*
* @param bundle
* @return
*/
public BaseSliderView bundle(Bundle bundle) {
mBundle = bundle;
return this;
}
public String getUrl() {
return mUrl;
}
public boolean isErrorDisappear() {
return mErrorDisappear;
}
public int getEmpty() {
return mEmptyPlaceHolderRes;
}
public int getError() {
return mErrorPlaceHolderRes;
}
public String getDescription() {
return mDescription;
}
public Context getContext() {
return mContext;
}
/**
* set a slider logo_image click listener
*
* @param l
* @return
*/
public BaseSliderView setOnSliderClickListener(OnSliderClickListener l) {
mOnSliderClickListener = l;
return this;
}
/**
* When you want to implement your own slider view, please call this method in the end in `getView()` method
*
* @param v the whole view
* @param targetImageView where to place logo_image
*/
protected void bindEventAndShow(final View v, ImageView targetImageView) {
final BaseSliderView me = this;
this.v = v;
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnSliderClickListener != null) {
mOnSliderClickListener.onSliderClick(me);
}
}
});
if (targetImageView == null)
return;
if (mLoadListener != null) {
mLoadListener.onStart(me);
}
// Picasso p = (mPicasso != null) ? mPicasso : Picasso.get();
if (mUrl != null) {
Glide.with(getContext()).load(mUrl).apply(new RequestOptions().placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.centerCrop().diskCacheStrategy(DiskCacheStrategy.RESOURCE)).listener(new MyImageRequestListener(this)).into(targetImageView);
} else if (mFile != null) {
//rq = p.load(mFile);
Glide.with(getContext()).load(mFile).apply(new RequestOptions().placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.centerCrop().diskCacheStrategy(DiskCacheStrategy.RESOURCE)).listener(new MyImageRequestListener(this)).into(targetImageView);
} else if (mRes != 0) {
// rq = p.load(mRes);
Glide.with(getContext()).load(mRes).apply(new RequestOptions().placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.centerCrop().diskCacheStrategy(DiskCacheStrategy.RESOURCE)).listener(new MyImageRequestListener(this)).into(targetImageView);
if (v.findViewById(R.id.loading_bar) != null) {
v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
}
} else {
return;
}
// if (rq == null) {
// return;
// }
//
// if (getEmpty() != 0) {
// rq.placeholder(getEmpty());
// }
//
// if (getError() != 0) {
// rq.error(getError());
// }
//
// switch (mScaleType) {
// case Fit:
// rq.fit();
// break;
// case CenterCrop:
// rq.fit().centerCrop();
// break;
// case CenterInside:
// rq.fit().centerInside();
// break;
// }
//
// rq.into(targetImageView, new Callback() {
// @Override
// public void onSuccess() {
// if (v.findViewById(R.id.loading_bar) != null) {
// v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
// }
// }
//
// @Override
// public void onError(Exception e) {
// if (mLoadListener != null) {
// mLoadListener.onEnd(false, me);
// }
// if (v.findViewById(R.id.loading_bar) != null) {
// v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
// }
// }
//
//
// });
}
public ScaleType getScaleType() {
return mScaleType;
}
public BaseSliderView setScaleType(ScaleType type) {
mScaleType = type;
return this;
}
/**
* the extended class have to implement getView(), which is called by the adapter,
* every extended class response to render their own view.
*
* @return
*/
public abstract View getView();
/**
* set a listener to get a message , if load error.
*
* @param l
*/
public void setOnImageLoadListener(ImageLoadListener l) {
mLoadListener = l;
}
/**
* when you have some extra information, please put it in this bundle.
*
* @return
*/
public Bundle getBundle() {
return mBundle;
}
@Override
public void onFailure(@Nullable String message) {
}
@Override
public void onSuccess(@NotNull String dataSource) {
if (v.findViewById(R.id.loading_bar) != null) {
v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
}
}
/**
* Get the last instance set via setPicasso(), or null if no user provided instance was set
*
* @return The current user-provided Picasso instance, or null if none
*/
public enum ScaleType {
CenterCrop, CenterInside, Fit, FitCenterCrop
}
public interface OnSliderClickListener {
void onSliderClick(BaseSliderView slider);
}
public interface ImageLoadListener {
void onStart(BaseSliderView target);
void onEnd(boolean result, BaseSliderView target);
}
}
import android.view.View;
import com.kogicodes.sokoni.utils.slider.Animations.BaseAnimationInterface;
import com.kogicodes.sokoni.utils.slider.Tricks.ViewPagerEx;
import com.nineoldandroids.view.ViewHelper;
import java.util.ArrayList;
import java.util.HashMap;
public abstract class BaseTransformer implements ViewPagerEx.PageTransformer {
boolean isApp, isDis;
private BaseAnimationInterface mCustomAnimationInterface;
private HashMap<View, ArrayList<Float>> h = new HashMap<View, ArrayList<Float>>();
/**
* Called each {@link #transformPage(View, float)}.
*
* @param view
* @param position
*/
protected abstract void onTransform(View view, float position);
@Override
public void transformPage(View view, float position) {
onPreTransform(view, position);
onTransform(view, position);
onPostTransform(view, position);
}
/**
* If the position offset of a fragment is less than negative one or greater than one, returning true will set the
* visibility of the fragment to {@link View#GONE}. Returning false will force the fragment to {@link View#VISIBLE}.
*
* @return
*/
protected boolean hideOffscreenPages() {
return true;
}
/**
* Indicates if the default animations of the view pager should be used.
*
* @return
*/
protected boolean isPagingEnabled() {
return false;
}
/**
* Called each {@link #transformPage(View, float)} before {{@link #onTransform(View, float)} is called.
*
* @param view
* @param position
*/
protected void onPreTransform(View view, float position) {
final float width = view.getWidth();
ViewHelper.setRotationX(view, 0);
ViewHelper.setRotationY(view, 0);
ViewHelper.setRotation(view, 0);
ViewHelper.setScaleX(view, 1);
ViewHelper.setScaleY(view, 1);
ViewHelper.setPivotX(view, 0);
ViewHelper.setPivotY(view, 0);
ViewHelper.setTranslationY(view, 0);
ViewHelper.setTranslationX(view, isPagingEnabled() ? 0f : -width * position);
if (hideOffscreenPages()) {
ViewHelper.setAlpha(view, position <= -1f || position >= 1f ? 0f : 1f);
} else {
ViewHelper.setAlpha(view, 1f);
}
if (mCustomAnimationInterface != null) {
if (h.containsKey(view) == false || h.get(view).size() == 1) {
if (position > -1 && position < 1) {
if (h.get(view) == null) {
h.put(view, new ArrayList<Float>());
}
h.get(view).add(position);
if (h.get(view).size() == 2) {
float zero = h.get(view).get(0);
float cha = h.get(view).get(1) - h.get(view).get(0);
if (zero > 0) {
if (cha > -1 && cha < 0) {
//in
mCustomAnimationInterface.onPrepareNextItemShowInScreen(view);
} else {
//out
mCustomAnimationInterface.onPrepareCurrentItemLeaveScreen(view);
}
} else {
if (cha > -1 && cha < 0) {
//out
mCustomAnimationInterface.onPrepareCurrentItemLeaveScreen(view);
} else {
//in
mCustomAnimationInterface.onPrepareNextItemShowInScreen(view);
}
}
}
}
}
}
}
/**
* Called each {@link #transformPage(View, float)} call after {@link #onTransform(View, float)} is finished.
*
* @param view
* @param position
*/
protected void onPostTransform(View view, float position) {
if (mCustomAnimationInterface != null) {
if (position == -1 || position == 1) {
mCustomAnimationInterface.onCurrentItemDisappear(view);
isApp = true;
} else if (position == 0) {
mCustomAnimationInterface.onNextItemAppear(view);
isDis = true;
}
if (isApp && isDis) {
h.clear();
isApp = false;
isDis = false;
}
}
}
public void setCustomAnimationInterface(BaseAnimationInterface animationInterface) {
mCustomAnimationInterface = animationInterface;
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class CubeInTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
// Rotate the fragment on the left or right edge
ViewHelper.setPivotX(view, position > 0 ? 0 : view.getWidth());
ViewHelper.setPivotY(view, 0);
ViewHelper.setRotation(view, -90f * position);
}
@Override
public boolean isPagingEnabled() {
return true;
}
}
package com.kogicodes.sokoni.utils.slider.SliderTypes;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import com.kogicodes.sokoni.R;
/**
* a simple slider view, which just show an logo_image. If you want to make your own slider view,
* <p>
* just extend BaseSliderView, and implement getView() method.
*/
public class DefaultSliderView extends BaseSliderView {
public DefaultSliderView(Context context) {
super(context);
}
@Override
public View getView() {
View v = LayoutInflater.from(getContext()).inflate(R.layout.render_type_default, null);
ImageView target = v.findViewById(R.id.daimajia_slider_image);
bindEventAndShow(v, target);
return v;
}
}
import android.view.View;
public class DefaultTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
}
@Override
public boolean isPagingEnabled() {
return true;
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class DepthPageTransformer extends BaseTransformer {
private static final float MIN_SCALE = 0.75f;
@Override
protected void onTransform(View view, float position) {
if (position <= 0f) {
ViewHelper.setTranslationX(view, 0f);
ViewHelper.setScaleX(view, 1f);
ViewHelper.setScaleY(view, 1f);
} else if (position <= 1f) {
final float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
ViewHelper.setAlpha(view, 1 - position);
ViewHelper.setPivotY(view, 0.5f * view.getHeight());
ViewHelper.setTranslationX(view, view.getWidth() * -position);
ViewHelper.setScaleX(view, scaleFactor);
ViewHelper.setScaleY(view, scaleFactor);
}
}
@Override
protected boolean isPagingEnabled() {
return true;
}
}
import android.view.View;
import com.kogicodes.sokoni.R;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.view.ViewHelper;
public class DescriptionAnimation implements BaseAnimationInterface {
@Override
public void onPrepareCurrentItemLeaveScreen(View current) {
View descriptionLayout = current.findViewById(R.id.description_layout);
if (descriptionLayout != null) {
current.findViewById(R.id.description_layout).setVisibility(View.INVISIBLE);
}
}
/**
* When next item is coming to show, let's hide the description layout.
*
* @param next
*/
@Override
public void onPrepareNextItemShowInScreen(View next) {
View descriptionLayout = next.findViewById(R.id.description_layout);
if (descriptionLayout != null) {
next.findViewById(R.id.description_layout).setVisibility(View.INVISIBLE);
}
}
@Override
public void onCurrentItemDisappear(View view) {
}
/**
* When next item show in ViewPagerEx, let's make an animation to show the
* description layout.
*
* @param view
*/
@Override
public void onNextItemAppear(View view) {
View descriptionLayout = view.findViewById(R.id.description_layout);
if (descriptionLayout != null) {
float layoutY = ViewHelper.getY(descriptionLayout);
view.findViewById(R.id.description_layout).setVisibility(View.VISIBLE);
ValueAnimator animator = ObjectAnimator.ofFloat(
descriptionLayout, "y", layoutY + descriptionLayout.getHeight(),
layoutY).setDuration(500);
animator.start();
}
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class FadeTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
// Page is not an immediate sibling, just make transparent
if (position < -1 || position > 1) {
ViewHelper.setAlpha(view, 0.6f);
}
// Page is sibling to left or right
else if (position <= 0 || position <= 1) {
// Calculate alpha. Position is decimal in [-1,0] or [0,1]
float alpha = (position <= 0) ? position + 1 : 1 - position;
ViewHelper.setAlpha(view, alpha);
}
// Page is active, make fully visible
else if (position == 0) {
ViewHelper.setAlpha(view, 1);
}
}
}
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
public class FixedSpeedScroller extends Scroller {
private int mDuration = 1000;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, int period) {
this(context, interpolator);
mDuration = period;
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class FlipHorizontalTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
final float rotation = 180f * position;
ViewHelper.setAlpha(view, rotation > 90f || rotation < -90f ? 0 : 1);
ViewHelper.setPivotY(view, view.getHeight() * 0.5f);
ViewHelper.setPivotX(view, view.getWidth() * 0.5f);
ViewHelper.setRotationY(view, rotation);
}
}
import android.os.Build;
import android.view.View;
import com.kogicodes.sokoni.utils.slider.Tricks.ViewPagerEx;
import com.nineoldandroids.view.ViewHelper;
public class FlipPageViewTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
float percentage = 1 - Math.abs(position);
if (Build.VERSION.SDK_INT >= 13) {
view.setCameraDistance(12000);
}
setVisibility(view, position);
setTranslation(view);
setSize(view, position, percentage);
setRotation(view, position, percentage);
}
private void setVisibility(View page, float position) {
if (position < 0.5 && position > -0.5) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
}
private void setTranslation(View view) {
ViewPagerEx viewPager = (ViewPagerEx) view.getParent();
int scroll = viewPager.getScrollX() - view.getLeft();
ViewHelper.setTranslationX(view, scroll);
}
private void setSize(View view, float position, float percentage) {
ViewHelper.setScaleX(view, (position != 0 && position != 1) ? percentage : 1);
ViewHelper.setScaleY(view, (position != 0 && position != 1) ? percentage : 1);
}
private void setRotation(View view, float position, float percentage) {
if (position > 0) {
ViewHelper.setRotationY(view, -180 * (percentage + 1));
} else {
ViewHelper.setRotationY(view, 180 * (percentage + 1));
}
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class ForegroundToBackgroundTransformer extends BaseTransformer {
private static final float min(float val, float min) {
return val < min ? min : val;
}
@Override
protected void onTransform(View view, float position) {
final float height = view.getHeight();
final float width = view.getWidth();
final float scale = min(position > 0 ? 1f : Math.abs(1f + position), 0.5f);
ViewHelper.setScaleX(view, scale);
ViewHelper.setScaleY(view, scale);
ViewHelper.setPivotX(view, width * 0.5f);
ViewHelper.setPivotY(view, height * 0.5f);
ViewHelper.setTranslationX(view, position > 0 ? width * position : -width * position * 0.25f);
}
}
package com.kogicodes.sokoni.utils.slider.Tricks;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.kogicodes.sokoni.utils.slider.SliderAdapter;
import androidx.viewpager.widget.PagerAdapter;
/**
* A PagerAdapter that wraps around another PagerAdapter to handle paging wrap-around.
* Thanks to: https://github.com/antonyt/InfiniteViewPager
*/
public class InfinitePagerAdapter extends PagerAdapter {
private static final String TAG = "InfinitePagerAdapter";
private static final boolean DEBUG = false;
private SliderAdapter adapter;
public InfinitePagerAdapter(SliderAdapter adapter) {
this.adapter = adapter;
}
public SliderAdapter getRealAdapter() {
return this.adapter;
}
@Override
public int getCount() {
// warning: scrolling to very high values (1,000,000+) results in
// strange drawing behaviour
return Integer.MAX_VALUE;
}
/**
* @return the {@link #getCount()} result of the wrapped adapter
*/
public int getRealCount() {
return adapter.getCount();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (getRealCount() == 0) {
return null;
}
int virtualPosition = position % getRealCount();
debug("instantiateItem: real position: " + position);
debug("instantiateItem: virtual position: " + virtualPosition);
// only expose virtual position to the inner adapter
return adapter.instantiateItem(container, virtualPosition);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (getRealCount() == 0) {
return;
}
int virtualPosition = position % getRealCount();
debug("destroyItem: real position: " + position);
debug("destroyItem: virtual position: " + virtualPosition);
// only expose virtual position to the inner adapter
adapter.destroyItem(container, virtualPosition, object);
}
/*
* Delegate rest of methods directly to the inner adapter.
*/
@Override
public void finishUpdate(ViewGroup container) {
adapter.finishUpdate(container);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return adapter.isViewFromObject(view, object);
}
@Override
public void restoreState(Parcelable bundle, ClassLoader classLoader) {
adapter.restoreState(bundle, classLoader);
}
@Override
public Parcelable saveState() {
return adapter.saveState();
}
@Override
public void startUpdate(ViewGroup container) {
adapter.startUpdate(container);
}
/*
* End delegation
*/
private void debug(String message) {
if (DEBUG) {
Log.d(TAG, message);
}
}
}
import android.content.Context;
import android.util.AttributeSet;
import androidx.viewpager.widget.PagerAdapter;
public class InfiniteViewPager extends ViewPagerEx {
public InfiniteViewPager(Context context) {
super(context);
}
public InfiniteViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setAdapter(PagerAdapter adapter) {
super.setAdapter(adapter);
}
}
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.kogicodes.sokoni.R;
import com.kogicodes.sokoni.utils.slider.Tricks.InfinitePagerAdapter;
import com.kogicodes.sokoni.utils.slider.Tricks.ViewPagerEx;
import java.util.ArrayList;
import androidx.viewpager.widget.PagerAdapter;
/**
* Pager Indicator.
*/
public class PagerIndicator extends LinearLayout implements ViewPagerEx.OnPageChangeListener {
private Context mContext;
/**
* bind this Indicator with {@link ViewPagerEx}
*/
private ViewPagerEx mPager;
/**
* Variable to remember the previous selected indicator.
*/
private ImageView mPreviousSelectedIndicator;
/**
* Previous selected indicator position.
*/
private int mPreviousSelectedPosition;
/**
* Custom selected indicator style resource id.
*/
private int mUserSetUnSelectedIndicatorResId;
/**
* Custom unselected indicator style resource id.
*/
private int mUserSetSelectedIndicatorResId;
private Drawable mSelectedDrawable;
private Drawable mUnselectedDrawable;
/**
* This value is from {@link com.daimajia.slider.library.SliderAdapter} getRealCount() represent
* <p>
* the indicator count that we should draw.
*/
private int mItemCount = 0;
private Shape mIndicatorShape = Shape.Oval;
private IndicatorVisibility mVisibility = IndicatorVisibility.Visible;
private int mDefaultSelectedColor;
private int mDefaultUnSelectedColor;
private float mDefaultSelectedWidth;
private float mDefaultSelectedHeight;
private float mDefaultUnSelectedWidth;
private float mDefaultUnSelectedHeight;
private GradientDrawable mUnSelectedGradientDrawable;
private GradientDrawable mSelectedGradientDrawable;
private LayerDrawable mSelectedLayerDrawable;
private LayerDrawable mUnSelectedLayerDrawable;
private float mPadding_left;
private float mPadding_right;
private float mPadding_top;
private float mPadding_bottom;
private float mSelectedPadding_Left;
private float mSelectedPadding_Right;
private float mSelectedPadding_Top;
private float mSelectedPadding_Bottom;
private float mUnSelectedPadding_Left;
private float mUnSelectedPadding_Right;
private float mUnSelectedPadding_Top;
private float mUnSelectedPadding_Bottom;
/**
* Put all the indicators into a ArrayList, so we can remove them easily.
*/
private ArrayList<ImageView> mIndicators = new ArrayList<ImageView>();
private DataSetObserver dataChangeObserver = new DataSetObserver() {
@Override
public void onChanged() {
PagerAdapter adapter = mPager.getAdapter();
int count = 0;
if (adapter instanceof InfinitePagerAdapter) {
count = ((InfinitePagerAdapter) adapter).getRealCount();
} else {
count = adapter.getCount();
}
if (count > mItemCount) {
for (int i = 0; i < count - mItemCount; i++) {
ImageView indicator = new ImageView(mContext);
indicator.setImageDrawable(mUnselectedDrawable);
indicator.setPadding((int) mUnSelectedPadding_Left,
(int) mUnSelectedPadding_Top,
(int) mUnSelectedPadding_Right,
(int) mUnSelectedPadding_Bottom);
addView(indicator);
mIndicators.add(indicator);
}
} else if (count < mItemCount) {
for (int i = 0; i < mItemCount - count; i++) {
removeView(mIndicators.get(0));
mIndicators.remove(0);
}
}
mItemCount = count;
mPager.setCurrentItem(mItemCount * 20 + mPager.getCurrentItem());
}
@Override
public void onInvalidated() {
super.onInvalidated();
redraw();
}
};
public PagerIndicator(Context context) {
this(context, null);
}
public PagerIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
final TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.PagerIndicator, 0, 0);
int visibility = attributes.getInt(R.styleable.PagerIndicator_visibility, IndicatorVisibility.Visible.ordinal());
for (IndicatorVisibility v : IndicatorVisibility.values()) {
if (v.ordinal() == visibility) {
mVisibility = v;
break;
}
}
int shape = attributes.getInt(R.styleable.PagerIndicator_shape, Shape.Oval.ordinal());
for (Shape s : Shape.values()) {
if (s.ordinal() == shape) {
mIndicatorShape = s;
break;
}
}
mUserSetSelectedIndicatorResId = attributes.getResourceId(R.styleable.PagerIndicator_selected_drawable,
0);
mUserSetUnSelectedIndicatorResId = attributes.getResourceId(R.styleable.PagerIndicator_unselected_drawable,
0);
mDefaultSelectedColor = attributes.getColor(R.styleable.PagerIndicator_selected_color, Color.rgb(255, 255, 255));
mDefaultUnSelectedColor = attributes.getColor(R.styleable.PagerIndicator_unselected_color, Color.argb(33, 255, 255, 255));
mDefaultSelectedWidth = attributes.getDimension(R.styleable.PagerIndicator_selected_width, (int) pxFromDp(6));
mDefaultSelectedHeight = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_selected_height, (int) pxFromDp(6));
mDefaultUnSelectedWidth = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_width, (int) pxFromDp(6));
mDefaultUnSelectedHeight = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_height, (int) pxFromDp(6));
mSelectedGradientDrawable = new GradientDrawable();
mUnSelectedGradientDrawable = new GradientDrawable();
mPadding_left = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_padding_left, (int) pxFromDp(3));
mPadding_right = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_padding_right, (int) pxFromDp(3));
mPadding_top = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_padding_top, (int) pxFromDp(0));
mPadding_bottom = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_padding_bottom, (int) pxFromDp(0));
mSelectedPadding_Left = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_selected_padding_left, (int) mPadding_left);
mSelectedPadding_Right = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_selected_padding_right, (int) mPadding_right);
mSelectedPadding_Top = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_selected_padding_top, (int) mPadding_top);
mSelectedPadding_Bottom = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_selected_padding_bottom, (int) mPadding_bottom);
mUnSelectedPadding_Left = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_padding_left, (int) mPadding_left);
mUnSelectedPadding_Right = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_padding_right, (int) mPadding_right);
mUnSelectedPadding_Top = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_padding_top, (int) mPadding_top);
mUnSelectedPadding_Bottom = attributes.getDimensionPixelSize(R.styleable.PagerIndicator_unselected_padding_bottom, (int) mPadding_bottom);
mSelectedLayerDrawable = new LayerDrawable(new Drawable[]{mSelectedGradientDrawable});
mUnSelectedLayerDrawable = new LayerDrawable(new Drawable[]{mUnSelectedGradientDrawable});
setIndicatorStyleResource(mUserSetSelectedIndicatorResId, mUserSetUnSelectedIndicatorResId);
setDefaultIndicatorShape(mIndicatorShape);
setDefaultSelectedIndicatorSize(mDefaultSelectedWidth, mDefaultSelectedHeight, Unit.Px);
setDefaultUnselectedIndicatorSize(mDefaultUnSelectedWidth, mDefaultUnSelectedHeight, Unit.Px);
setDefaultIndicatorColor(mDefaultSelectedColor, mDefaultUnSelectedColor);
setIndicatorVisibility(mVisibility);
attributes.recycle();
}
/**
* if you are using the default indicator, this method will help you to set the shape of
* indicator, there are two kind of shapes you can set, oval and rect.
*
* @param shape
*/
public void setDefaultIndicatorShape(Shape shape) {
if (mUserSetSelectedIndicatorResId == 0) {
if (shape == Shape.Oval) {
mSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
} else {
mSelectedGradientDrawable.setShape(GradientDrawable.RECTANGLE);
}
}
if (mUserSetUnSelectedIndicatorResId == 0) {
if (shape == Shape.Oval) {
mUnSelectedGradientDrawable.setShape(GradientDrawable.OVAL);
} else {
mUnSelectedGradientDrawable.setShape(GradientDrawable.RECTANGLE);
}
}
resetDrawable();
}
/**
* Set Indicator style.
*
* @param selected page selected drawable
* @param unselected page unselected drawable
*/
public void setIndicatorStyleResource(int selected, int unselected) {
mUserSetSelectedIndicatorResId = selected;
mUserSetUnSelectedIndicatorResId = unselected;
if (selected == 0) {
mSelectedDrawable = mSelectedLayerDrawable;
} else {
mSelectedDrawable = mContext.getResources().getDrawable(mUserSetSelectedIndicatorResId);
}
if (unselected == 0) {
mUnselectedDrawable = mUnSelectedLayerDrawable;
} else {
mUnselectedDrawable = mContext.getResources().getDrawable(mUserSetUnSelectedIndicatorResId);
}
resetDrawable();
}
/**
* if you are using the default indicator , this method will help you to set the selected status and
* the unselected status color.
*
* @param selectedColor
* @param unselectedColor
*/
public void setDefaultIndicatorColor(int selectedColor, int unselectedColor) {
if (mUserSetSelectedIndicatorResId == 0) {
mSelectedGradientDrawable.setColor(selectedColor);
}
if (mUserSetUnSelectedIndicatorResId == 0) {
mUnSelectedGradientDrawable.setColor(unselectedColor);
}
resetDrawable();
}
public void setDefaultSelectedIndicatorSize(float width, float height, Unit unit) {
if (mUserSetSelectedIndicatorResId == 0) {
float w = width;
float h = height;
if (unit == Unit.DP) {
w = pxFromDp(width);
h = pxFromDp(height);
}
mSelectedGradientDrawable.setSize((int) w, (int) h);
resetDrawable();
}
}
public void setDefaultUnselectedIndicatorSize(float width, float height, Unit unit) {
if (mUserSetUnSelectedIndicatorResId == 0) {
float w = width;
float h = height;
if (unit == Unit.DP) {
w = pxFromDp(width);
h = pxFromDp(height);
}
mUnSelectedGradientDrawable.setSize((int) w, (int) h);
resetDrawable();
}
}
public void setDefaultIndicatorSize(float width, float height, Unit unit) {
setDefaultSelectedIndicatorSize(width, height, unit);
setDefaultUnselectedIndicatorSize(width, height, unit);
}
private float dpFromPx(float px) {
return px / this.getContext().getResources().getDisplayMetrics().density;
}
private float pxFromDp(float dp) {
return dp * this.getContext().getResources().getDisplayMetrics().density;
}
/**
* clear self means unregister the dataset observer and remove all the child views(indicators).
*/
public void destroySelf() {
if (mPager == null || mPager.getAdapter() == null) {
return;
}
InfinitePagerAdapter wrapper = (InfinitePagerAdapter) mPager.getAdapter();
PagerAdapter adapter = wrapper.getRealAdapter();
if (adapter != null) {
adapter.unregisterDataSetObserver(dataChangeObserver);
}
removeAllViews();
}
/**
* bind indicator with viewpagerEx.
*
* @param pager
*/
public void setViewPager(ViewPagerEx pager) {
if (pager.getAdapter() == null) {
throw new IllegalStateException("Viewpager does not have adapter instance");
}
mPager = pager;
mPager.addOnPageChangeListener(this);
((InfinitePagerAdapter) mPager.getAdapter()).getRealAdapter().registerDataSetObserver(dataChangeObserver);
}
private void resetDrawable() {
for (View i : mIndicators) {
if (mPreviousSelectedIndicator != null && mPreviousSelectedIndicator.equals(i)) {
((ImageView) i).setImageDrawable(mSelectedDrawable);
} else {
((ImageView) i).setImageDrawable(mUnselectedDrawable);
}
}
}
/**
* redraw the indicators.
*/
public void redraw() {
mItemCount = getShouldDrawCount();
mPreviousSelectedIndicator = null;
for (View i : mIndicators) {
removeView(i);
}
for (int i = 0; i < mItemCount; i++) {
ImageView indicator = new ImageView(mContext);
indicator.setImageDrawable(mUnselectedDrawable);
indicator.setPadding((int) mUnSelectedPadding_Left,
(int) mUnSelectedPadding_Top,
(int) mUnSelectedPadding_Right,
(int) mUnSelectedPadding_Bottom);
addView(indicator);
mIndicators.add(indicator);
}
setItemAsSelected(mPreviousSelectedPosition);
}
/**
* since we used a adapter wrapper, so we can't getCount directly from wrapper.
*
* @return
*/
private int getShouldDrawCount() {
if (mPager.getAdapter() instanceof InfinitePagerAdapter) {
return ((InfinitePagerAdapter) mPager.getAdapter()).getRealCount();
} else {
return mPager.getAdapter().getCount();
}
}
private void setItemAsSelected(int position) {
if (mPreviousSelectedIndicator != null) {
mPreviousSelectedIndicator.setImageDrawable(mUnselectedDrawable);
mPreviousSelectedIndicator.setPadding(
(int) mUnSelectedPadding_Left,
(int) mUnSelectedPadding_Top,
(int) mUnSelectedPadding_Right,
(int) mUnSelectedPadding_Bottom
);
}
ImageView currentSelected = (ImageView) getChildAt(position + 1);
if (currentSelected != null) {
currentSelected.setImageDrawable(mSelectedDrawable);
currentSelected.setPadding(
(int) mSelectedPadding_Left,
(int) mSelectedPadding_Top,
(int) mSelectedPadding_Right,
(int) mSelectedPadding_Bottom
);
mPreviousSelectedIndicator = currentSelected;
}
mPreviousSelectedPosition = position;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public IndicatorVisibility getIndicatorVisibility() {
return mVisibility;
}
/**
* set the visibility of indicator.
*
* @param visibility
*/
public void setIndicatorVisibility(IndicatorVisibility visibility) {
if (visibility == IndicatorVisibility.Visible) {
setVisibility(View.VISIBLE);
} else {
setVisibility(View.INVISIBLE);
}
resetDrawable();
}
@Override
public void onPageSelected(int position) {
if (mItemCount == 0) {
return;
}
setItemAsSelected(position - 1);
}
@Override
public void onPageScrollStateChanged(int state) {
}
public int getSelectedIndicatorResId() {
return mUserSetSelectedIndicatorResId;
}
public int getUnSelectedIndicatorResId() {
return mUserSetUnSelectedIndicatorResId;
}
public enum IndicatorVisibility {
Visible,
Invisible
}
public enum Shape {
Oval, Rectangle
}
public enum Unit {
DP, Px
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class RotateDownTransformer extends BaseTransformer {
private static final float ROT_MOD = -15f;
@Override
protected void onTransform(View view, float position) {
final float width = view.getWidth();
final float height = view.getHeight();
final float rotation = ROT_MOD * position * -1.25f;
ViewHelper.setPivotX(view, width * 0.5f);
ViewHelper.setPivotY(view, height);
ViewHelper.setRotation(view, rotation);
}
@Override
protected boolean isPagingEnabled() {
return true;
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class RotateUpTransformer extends BaseTransformer {
private static final float ROT_MOD = -15f;
@Override
protected void onTransform(View view, float position) {
final float width = view.getWidth();
final float rotation = ROT_MOD * position;
ViewHelper.setPivotX(view, width * 0.5f);
ViewHelper.setPivotY(view, 0f);
ViewHelper.setTranslationX(view, 0f);
ViewHelper.setRotation(view, rotation);
}
@Override
protected boolean isPagingEnabled() {
return true;
}
}
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import com.kogicodes.sokoni.utils.slider.SliderTypes.BaseSliderView;
import java.util.ArrayList;
import androidx.viewpager.widget.PagerAdapter;
/**
* A slider adapter
*/
public class SliderAdapter extends PagerAdapter implements BaseSliderView.ImageLoadListener {
private Context mContext;
private ArrayList<BaseSliderView> mImageContents;
public SliderAdapter(Context context) {
mContext = context;
mImageContents = new ArrayList<BaseSliderView>();
}
public <T extends BaseSliderView> void addSlider(T slider) {
slider.setOnImageLoadListener(this);
mImageContents.add(slider);
notifyDataSetChanged();
}
public BaseSliderView getSliderView(int position) {
if (position < 0 || position >= mImageContents.size()) {
return null;
} else {
return mImageContents.get(position);
}
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public <T extends BaseSliderView> void removeSlider(T slider) {
if (mImageContents.contains(slider)) {
mImageContents.remove(slider);
notifyDataSetChanged();
}
}
public void removeSliderAt(int position) {
if (mImageContents.size() > position) {
mImageContents.remove(position);
notifyDataSetChanged();
}
}
public void removeAllSliders() {
mImageContents.clear();
notifyDataSetChanged();
}
@Override
public int getCount() {
return mImageContents.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
BaseSliderView b = mImageContents.get(position);
View v = b.getView();
container.addView(v);
return v;
}
@Override
public void onStart(BaseSliderView target) {
}
/**
* When logo_image download error, then remove.
*
* @param result
* @param target
*/
@Override
public void onEnd(boolean result, BaseSliderView target) {
if (target.isErrorDisappear() == false || result == true) {
return;
}
for (BaseSliderView slider : mImageContents) {
if (slider.equals(target)) {
removeSlider(target);
break;
}
}
}
}
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.RelativeLayout;
import com.kogicodes.sokoni.R;
import com.kogicodes.sokoni.utils.slider.Animations.BaseAnimationInterface;
import com.kogicodes.sokoni.utils.slider.Indicators.PagerIndicator;
import com.kogicodes.sokoni.utils.slider.SliderTypes.BaseSliderView;
import com.kogicodes.sokoni.utils.slider.Transformers.AccordionTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.BackgroundToForegroundTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.BaseTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.CubeInTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.DefaultTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.DepthPageTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.FadeTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.FlipHorizontalTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.FlipPageViewTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.ForegroundToBackgroundTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.RotateDownTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.RotateUpTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.StackTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.TabletTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.ZoomInTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.ZoomOutSlideTransformer;
import com.kogicodes.sokoni.utils.slider.Transformers.ZoomOutTransformer;
import com.kogicodes.sokoni.utils.slider.Tricks.FixedSpeedScroller;
import com.kogicodes.sokoni.utils.slider.Tricks.InfinitePagerAdapter;
import com.kogicodes.sokoni.utils.slider.Tricks.InfiniteViewPager;
import com.kogicodes.sokoni.utils.slider.Tricks.ViewPagerEx;
import java.lang.reflect.Field;
import java.util.Timer;
import java.util.TimerTask;
import androidx.viewpager.widget.PagerAdapter;
/**
* SliderLayout is compound layout. This is combined with {@link PagerIndicator}
* and {@link ViewPagerEx} .
* <p>
* There is some properties you can set in XML:
* <p>
* indicator_visibility
* visible
* invisible
* <p>
* indicator_shape
* oval
* rect
* <p>
* indicator_selected_color
* <p>
* indicator_unselected_color
* <p>
* indicator_selected_drawable
* <p>
* indicator_unselected_drawable
* <p>
* pager_animation
* Default
* Accordion
* Background2Foreground
* CubeIn
* DepthPage
* Fade
* FlipHorizontal
* FlipPage
* Foreground2Background
* RotateDown
* RotateUp
* Stack
* Tablet
* ZoomIn
* ZoomOutSlide
* ZoomOut
* <p>
* pager_animation_span
*/
public class SliderLayout extends RelativeLayout {
private Context mContext;
/**
* InfiniteViewPager is extended from ViewPagerEx. As the name says, it can scroll without bounder.
*/
private InfiniteViewPager mViewPager;
/**
* InfiniteViewPager adapter.
*/
private SliderAdapter mSliderAdapter;
/**
* {@link ViewPagerEx} indicator.
*/
private PagerIndicator mIndicator;
/**
* A timer and a TimerTask using to cycle the {@link ViewPagerEx}.
*/
private Timer mCycleTimer;
private TimerTask mCycleTask;
/**
* For resuming the cycle, after user touch or click the {@link ViewPagerEx}.
*/
private Timer mResumingTimer;
private TimerTask mResumingTask;
/**
* If {@link ViewPagerEx} is Cycling
*/
private boolean mCycling;
/**
* Determine if auto recover after user touch the {@link ViewPagerEx}
*/
private boolean mAutoRecover = true;
private int mTransformerId;
/**
* {@link ViewPagerEx} transformer time span.
*/
private int mTransformerSpan = 1100;
private boolean mAutoCycle;
/**
* the duration between animation.
*/
private long mSliderDuration = 4000;
/**
* Visibility of {@link PagerIndicator}
*/
private PagerIndicator.IndicatorVisibility mIndicatorVisibility = PagerIndicator.IndicatorVisibility.Visible;
/**
* {@link ViewPagerEx} 's transformer
*/
private BaseTransformer mViewPagerTransformer;
/**
* @see BaseAnimationInterface
*/
private BaseAnimationInterface mCustomAnimation;
private android.os.Handler mh = new android.os.Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
moveNextPosition(true);
}
};
/**
* {@link PagerIndicator} shape, rect or oval.
*/
public SliderLayout(Context context) {
this(context, null);
}
public SliderLayout(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.SliderStyle);
}
public SliderLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
LayoutInflater.from(context).inflate(R.layout.slider_layout, this, true);
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SliderLayout,
defStyle, 0);
mTransformerSpan = attributes.getInteger(R.styleable.SliderLayout_pager_animation_span, 1100);
mTransformerId = attributes.getInt(R.styleable.SliderLayout_pager_animation, Transformer.Default.ordinal());
mAutoCycle = attributes.getBoolean(R.styleable.SliderLayout_auto_cycle, true);
int visibility = attributes.getInt(R.styleable.SliderLayout_indicator_visibility, 0);
for (PagerIndicator.IndicatorVisibility v : PagerIndicator.IndicatorVisibility.values()) {
if (v.ordinal() == visibility) {
mIndicatorVisibility = v;
break;
}
}
mSliderAdapter = new SliderAdapter(mContext);
PagerAdapter wrappedAdapter = new InfinitePagerAdapter(mSliderAdapter);
mViewPager = findViewById(R.id.daimajia_slider_viewpager);
mViewPager.setAdapter(wrappedAdapter);
mViewPager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
recoverCycle();
break;
}
return false;
}
});
attributes.recycle();
setPresetIndicator(PresetIndicators.Center_Bottom);
setPresetTransformer(mTransformerId);
setSliderTransformDuration(mTransformerSpan, null);
setIndicatorVisibility(mIndicatorVisibility);
if (mAutoCycle) {
startAutoCycle();
}
}
public void addOnPageChangeListener(ViewPagerEx.OnPageChangeListener onPageChangeListener) {
if (onPageChangeListener != null) {
mViewPager.addOnPageChangeListener(onPageChangeListener);
}
}
public void removeOnPageChangeListener(ViewPagerEx.OnPageChangeListener onPageChangeListener) {
mViewPager.removeOnPageChangeListener(onPageChangeListener);
}
public void setCustomIndicator(PagerIndicator indicator) {
if (mIndicator != null) {
mIndicator.destroySelf();
}
mIndicator = indicator;
mIndicator.setIndicatorVisibility(mIndicatorVisibility);
mIndicator.setViewPager(mViewPager);
mIndicator.redraw();
}
public <T extends BaseSliderView> void addSlider(T imageContent) {
mSliderAdapter.addSlider(imageContent);
}
public void startAutoCycle() {
startAutoCycle(mSliderDuration, mSliderDuration, mAutoRecover);
}
/**
* start auto cycle.
*
* @param delay delay time
* @param duration animation duration time.
* @param autoRecover if recover after user touches the slider.
*/
public void startAutoCycle(long delay, long duration, boolean autoRecover) {
if (mCycleTimer != null) mCycleTimer.cancel();
if (mCycleTask != null) mCycleTask.cancel();
if (mResumingTask != null) mResumingTask.cancel();
if (mResumingTimer != null) mResumingTimer.cancel();
mSliderDuration = duration;
mCycleTimer = new Timer();
mAutoRecover = autoRecover;
mCycleTask = new TimerTask() {
@Override
public void run() {
mh.sendEmptyMessage(0);
}
};
mCycleTimer.schedule(mCycleTask, delay, mSliderDuration);
mCycling = true;
mAutoCycle = true;
}
/**
* pause auto cycle.
*/
private void pauseAutoCycle() {
if (mCycling) {
mCycleTimer.cancel();
mCycleTask.cancel();
mCycling = false;
} else {
if (mResumingTimer != null && mResumingTask != null) {
recoverCycle();
}
}
}
/**
* set the duration between two slider changes. the duration value must >= 500
*
* @param duration
*/
public void setDuration(long duration) {
if (duration >= 500) {
mSliderDuration = duration;
if (mAutoCycle && mCycling) {
startAutoCycle();
}
}
}
/**
* stop the auto circle
*/
public void stopAutoCycle() {
if (mCycleTask != null) {
mCycleTask.cancel();
}
if (mCycleTimer != null) {
mCycleTimer.cancel();
}
if (mResumingTimer != null) {
mResumingTimer.cancel();
}
if (mResumingTask != null) {
mResumingTask.cancel();
}
mAutoCycle = false;
mCycling = false;
}
/**
* when paused cycle, this method can weak it up.
*/
private void recoverCycle() {
if (!mAutoRecover || !mAutoCycle) {
return;
}
if (!mCycling) {
if (mResumingTask != null && mResumingTimer != null) {
mResumingTimer.cancel();
mResumingTask.cancel();
}
mResumingTimer = new Timer();
mResumingTask = new TimerTask() {
@Override
public void run() {
startAutoCycle();
}
};
mResumingTimer.schedule(mResumingTask, 6000);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
pauseAutoCycle();
break;
}
return false;
}
/**
* set ViewPager transformer.
*
* @param reverseDrawingOrder
* @param transformer
*/
public void setPagerTransformer(boolean reverseDrawingOrder, BaseTransformer transformer) {
mViewPagerTransformer = transformer;
mViewPagerTransformer.setCustomAnimationInterface(mCustomAnimation);
mViewPager.setPageTransformer(reverseDrawingOrder, mViewPagerTransformer);
}
/**
* set the duration between two slider changes.
*
* @param period
* @param interpolator
*/
public void setSliderTransformDuration(int period, Interpolator interpolator) {
try {
Field mScroller = ViewPagerEx.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
FixedSpeedScroller scroller = new FixedSpeedScroller(mViewPager.getContext(), interpolator, period);
mScroller.set(mViewPager, scroller);
} catch (Exception e) {
}
}
/**
* set a preset viewpager transformer by id.
*
* @param transformerId
*/
public void setPresetTransformer(int transformerId) {
for (Transformer t : Transformer.values()) {
if (t.ordinal() == transformerId) {
setPresetTransformer(t);
break;
}
}
}
/**
* set preset PagerTransformer via the name of transforemer.
*
* @param transformerName
*/
public void setPresetTransformer(String transformerName) {
for (Transformer t : Transformer.values()) {
if (t.equals(transformerName)) {
setPresetTransformer(t);
return;
}
}
}
/**
* Inject your custom animation into PageTransformer, you can know more details in
* {@link BaseAnimationInterface},
* and you can see a example in {@link Animations.DescriptionAnimation}
*
* @param animation
*/
public void setCustomAnimation(BaseAnimationInterface animation) {
mCustomAnimation = animation;
if (mViewPagerTransformer != null) {
mViewPagerTransformer.setCustomAnimationInterface(mCustomAnimation);
}
}
/**
* pretty much right? enjoy it. :-D
*
* @param ts
*/
public void setPresetTransformer(Transformer ts) {
//
// special thanks to https://github.com/ToxicBakery/ViewPagerTransforms
//
BaseTransformer t = null;
switch (ts) {
case Default:
t = new DefaultTransformer();
break;
case Accordion:
t = new AccordionTransformer();
break;
case Background2Foreground:
t = new BackgroundToForegroundTransformer();
break;
case CubeIn:
t = new CubeInTransformer();
break;
case DepthPage:
t = new DepthPageTransformer();
break;
case Fade:
t = new FadeTransformer();
break;
case FlipHorizontal:
t = new FlipHorizontalTransformer();
break;
case FlipPage:
t = new FlipPageViewTransformer();
break;
case Foreground2Background:
t = new ForegroundToBackgroundTransformer();
break;
case RotateDown:
t = new RotateDownTransformer();
break;
case RotateUp:
t = new RotateUpTransformer();
break;
case Stack:
t = new StackTransformer();
break;
case Tablet:
t = new TabletTransformer();
break;
case ZoomIn:
t = new ZoomInTransformer();
break;
case ZoomOutSlide:
t = new ZoomOutSlideTransformer();
break;
case ZoomOut:
t = new ZoomOutTransformer();
break;
}
setPagerTransformer(true, t);
}
public PagerIndicator.IndicatorVisibility getIndicatorVisibility() {
if (mIndicator == null) {
return mIndicator.getIndicatorVisibility();
}
return PagerIndicator.IndicatorVisibility.Invisible;
}
/**
* Set the visibility of the indicators.
*
* @param visibility
*/
public void setIndicatorVisibility(PagerIndicator.IndicatorVisibility visibility) {
if (mIndicator == null) {
return;
}
mIndicator.setIndicatorVisibility(visibility);
}
/**
* get the {@link PagerIndicator} instance.
* You can manipulate the properties of the indicator.
*
* @return
*/
public PagerIndicator getPagerIndicator() {
return mIndicator;
}
public void setPresetIndicator(PresetIndicators presetIndicator) {
PagerIndicator pagerIndicator = findViewById(presetIndicator.getResourceId());
setCustomIndicator(pagerIndicator);
}
private InfinitePagerAdapter getWrapperAdapter() {
PagerAdapter adapter = mViewPager.getAdapter();
if (adapter != null) {
return (InfinitePagerAdapter) adapter;
} else {
return null;
}
}
private SliderAdapter getRealAdapter() {
PagerAdapter adapter = mViewPager.getAdapter();
if (adapter != null) {
return ((InfinitePagerAdapter) adapter).getRealAdapter();
}
return null;
}
/**
* get the current item position
*
* @return
*/
public int getCurrentPosition() {
if (getRealAdapter() == null)
throw new IllegalStateException("You did not set a slider adapter");
return mViewPager.getCurrentItem() % getRealAdapter().getCount();
}
public void setCurrentPosition(int position) {
setCurrentPosition(position, true);
}
/**
* get current slider.
*
* @return
*/
public BaseSliderView getCurrentSlider() {
if (getRealAdapter() == null)
throw new IllegalStateException("You did not set a slider adapter");
int count = getRealAdapter().getCount();
int realCount = mViewPager.getCurrentItem() % count;
return getRealAdapter().getSliderView(realCount);
}
/**
* remove the slider at the position. Notice: It's a not perfect method, a very small bug still exists.
*/
public void removeSliderAt(int position) {
if (getRealAdapter() != null) {
getRealAdapter().removeSliderAt(position);
mViewPager.setCurrentItem(mViewPager.getCurrentItem(), false);
}
}
/**
* remove all the sliders. Notice: It's a not perfect method, a very small bug still exists.
*/
public void removeAllSliders() {
if (getRealAdapter() != null) {
int count = getRealAdapter().getCount();
getRealAdapter().removeAllSliders();
//a small bug, but fixed by this trick.
//bug: when remove adapter's all the sliders.some caching slider still alive.
mViewPager.setCurrentItem(mViewPager.getCurrentItem() + count, false);
}
}
/**
* set current slider
*
* @param position
*/
public void setCurrentPosition(int position, boolean smooth) {
if (getRealAdapter() == null)
throw new IllegalStateException("You did not set a slider adapter");
if (position >= getRealAdapter().getCount()) {
throw new IllegalStateException("Item position is not exist");
}
int p = mViewPager.getCurrentItem() % getRealAdapter().getCount();
int n = (position - p) + mViewPager.getCurrentItem();
mViewPager.setCurrentItem(n, smooth);
}
/**
* move to prev slide.
*/
public void movePrevPosition(boolean smooth) {
if (getRealAdapter() == null)
throw new IllegalStateException("You did not set a slider adapter");
mViewPager.setCurrentItem(mViewPager.getCurrentItem() - 1, smooth);
}
public void movePrevPosition() {
movePrevPosition(true);
}
/**
* move to next slide.
*/
public void moveNextPosition(boolean smooth) {
if (getRealAdapter() == null)
throw new IllegalStateException("You did not set a slider adapter");
mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1, smooth);
}
public void moveNextPosition() {
moveNextPosition(true);
}
/**
* preset transformers and their names
*/
public enum Transformer {
Default("Default"),
Accordion("Accordion"),
Background2Foreground("Background2Foreground"),
CubeIn("CubeIn"),
DepthPage("DepthPage"),
Fade("Fade"),
FlipHorizontal("FlipHorizontal"),
FlipPage("FlipPage"),
Foreground2Background("Foreground2Background"),
RotateDown("RotateDown"),
RotateUp("RotateUp"),
Stack("Stack"),
Tablet("Tablet"),
ZoomIn("ZoomIn"),
ZoomOutSlide("ZoomOutSlide"),
ZoomOut("ZoomOut");
private final String name;
Transformer(String s) {
name = s;
}
public String toString() {
return name;
}
public boolean equals(String other) {
return (other != null) && name.equals(other);
}
}
public enum PresetIndicators {
Center_Bottom("Center_Bottom", R.id.default_center_bottom_indicator),
Right_Bottom("Right_Bottom", R.id.default_bottom_right_indicator),
Left_Bottom("Left_Bottom", R.id.default_bottom_left_indicator),
Center_Top("Center_Top", R.id.default_center_top_indicator),
Right_Top("Right_Top", R.id.default_center_top_right_indicator),
Left_Top("Left_Top", R.id.default_center_top_left_indicator);
private final String name;
private final int id;
PresetIndicators(String name, int id) {
this.name = name;
this.id = id;
}
public String toString() {
return name;
}
public int getResourceId() {
return id;
}
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class StackTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
ViewHelper.setTranslationX(view, position < 0 ? 0f : -view.getWidth() * position);
}
}
import android.graphics.Camera;
import android.graphics.Matrix;
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class TabletTransformer extends BaseTransformer {
private static final Matrix OFFSET_MATRIX = new Matrix();
private static final Camera OFFSET_CAMERA = new Camera();
private static final float[] OFFSET_TEMP_FLOAT = new float[2];
protected static final float getOffsetXForRotation(float degrees, int width, int height) {
OFFSET_MATRIX.reset();
OFFSET_CAMERA.save();
OFFSET_CAMERA.rotateY(Math.abs(degrees));
OFFSET_CAMERA.getMatrix(OFFSET_MATRIX);
OFFSET_CAMERA.restore();
OFFSET_MATRIX.preTranslate(-width * 0.5f, -height * 0.5f);
OFFSET_MATRIX.postTranslate(width * 0.5f, height * 0.5f);
OFFSET_TEMP_FLOAT[0] = width;
OFFSET_TEMP_FLOAT[1] = height;
OFFSET_MATRIX.mapPoints(OFFSET_TEMP_FLOAT);
return (width - OFFSET_TEMP_FLOAT[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
}
@Override
protected void onTransform(View view, float position) {
final float rotation = (position < 0 ? 30f : -30f) * Math.abs(position);
ViewHelper.setTranslationX(view, getOffsetXForRotation(rotation, view.getWidth(), view.getHeight()));
ViewHelper.setPivotX(view, view.getWidth() * 0.5f);
ViewHelper.setPivotY(view, 0);
ViewHelper.setRotationY(view, rotation);
}
}
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kogicodes.sokoni.R;
/**
* This is a slider with a description TextView.
*/
public class TextSliderView extends BaseSliderView {
public TextSliderView(Context context) {
super(context);
}
@Override
public View getView() {
View v = LayoutInflater.from(getContext()).inflate(R.layout.render_type_text, null);
ImageView target = v.findViewById(R.id.daimajia_slider_image);
TextView description = v.findViewById(R.id.description);
description.setText(getDescription());
bindEventAndShow(v, target);
return v;
}
}
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import androidx.core.os.ParcelableCompat;
import androidx.core.os.ParcelableCompatCreatorCallbacks;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.MotionEventCompat;
import androidx.core.view.VelocityTrackerCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewConfigurationCompat;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.core.widget.EdgeEffectCompat;
import androidx.viewpager.widget.PagerAdapter;
/**
* Layout manager that allows the user to flip left and right
* through pages of data. You supply an implementation of a
* {@link PagerAdapter} to generate the pages that the view shows.
*
* <p>Note this class is currently under early design and
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.</p>
*
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
* {@link android.support.v4.app.FragmentPagerAdapter} and
* {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
*
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
* <p>
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
*/
/**
* @author daimajia : I just remove the if condition in setPageTransformer() to make it compatiable with Android 2.0+
* of course, with the help of the NineOldDroid.
* Thanks to JakeWharton.
* http://github.com/JakeWharton/NineOldAndroids
*/
public class ViewPagerEx extends ViewGroup {
/**
* Indicates that the pager is in an idle, settled state. The current page
* is fully in view and no animation is in progress.
*/
public static final int SCROLL_STATE_IDLE = 0;
/**
* Indicates that the pager is currently being dragged by the user.
*/
public static final int SCROLL_STATE_DRAGGING = 1;
/**
* Indicates that the pager is in the process of settling to a final position.
*/
public static final int SCROLL_STATE_SETTLING = 2;
private static final String TAG = "ViewPagerEx";
private static final boolean DEBUG = false;
private static final boolean USE_CACHE = false;
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private static final int MAX_SETTLE_DURATION = 600; // ms
private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
private static final int DEFAULT_GUTTER_SIZE = 16; // dips
private static final int MIN_FLING_VELOCITY = 400; // dips
private static final int[] LAYOUT_ATTRS = new int[]{
android.R.attr.layout_gravity
};
private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
return lhs.position - rhs.position;
}
};
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
// If the pager is at least this close to its final position, complete the scroll
// on touch down and let the user interact with the content inside instead of
// "catching" the flinging pager.
private static final int CLOSE_ENOUGH = 2; // dp
private static final int DRAW_ORDER_DEFAULT = 0;
private static final int DRAW_ORDER_FORWARD = 1;
private static final int DRAW_ORDER_REVERSE = 2;
private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
private final ItemInfo mTempItem = new ItemInfo();
private final Rect mTempRect = new Rect();
/**
* Used to track what the expected number of items in the adapter should be.
* If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
*/
private int mExpectedAdapterCount;
private PagerAdapter mAdapter;
private int mCurItem; // Index of currently displayed page.
private int mRestoredCurItem = -1;
private Parcelable mRestoredAdapterState = null;
private ClassLoader mRestoredClassLoader = null;
private Scroller mScroller;
private PagerObserver mObserver;
private int mPageMargin;
private Drawable mMarginDrawable;
private int mTopPageBounds;
private int mBottomPageBounds;
// Offsets of the first and last items, if known.
// Set during population, used to determine if we are at the beginning
// or end of the pager data set during touch scrolling.
private float mFirstOffset = -Float.MAX_VALUE;
private float mLastOffset = Float.MAX_VALUE;
private int mChildWidthMeasureSpec;
private int mChildHeightMeasureSpec;
private boolean mInLayout;
private boolean mScrollingCacheEnabled;
private boolean mPopulatePending;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
private boolean mIsBeingDragged;
private boolean mIsUnableToDrag;
private boolean mIgnoreGutter;
private int mDefaultGutterSize;
private int mGutterSize;
private int mTouchSlop;
/**
* Position of the last motion event.
*/
private float mLastMotionX;
private float mLastMotionY;
private float mInitialMotionX;
private float mInitialMotionY;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mFlingDistance;
private int mCloseEnough;
private boolean mFakeDragging;
private long mFakeDragBeginTime;
private EdgeEffectCompat mLeftEdge;
private EdgeEffectCompat mRightEdge;
private boolean mFirstLayout = true;
private boolean mNeedCalculatePageOffsets = false;
private boolean mCalledSuper;
private int mDecorChildCount;
private ArrayList<OnPageChangeListener> mOnPageChangeListeners = new ArrayList<>();
private OnPageChangeListener mInternalPageChangeListener;
private OnAdapterChangeListener mAdapterChangeListener;
private PageTransformer mPageTransformer;
private Method mSetChildrenDrawingOrderEnabled;
private int mDrawingOrder;
private ArrayList<View> mDrawingOrderedChildren;
private int mScrollState = SCROLL_STATE_IDLE;
private final Runnable mEndScrollRunnable = new Runnable() {
public void run() {
setScrollState(SCROLL_STATE_IDLE);
populate();
}
};
public ViewPagerEx(Context context) {
super(context);
initViewPager();
}
public ViewPagerEx(Context context, AttributeSet attrs) {
super(context, attrs);
initViewPager();
}
private void triggerOnPageChangeEvent(int position) {
for (OnPageChangeListener eachListener : mOnPageChangeListeners) {
if (eachListener != null) {
InfinitePagerAdapter infiniteAdapter = (InfinitePagerAdapter) mAdapter;
if (infiniteAdapter.getRealCount() == 0) {
return;
}
int n = position % infiniteAdapter.getRealCount();
eachListener.onPageSelected(n);
}
}
if (mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(position);
}
}
void initViewPager() {
setWillNotDraw(false);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
final Context context = getContext();
mScroller = new Scroller(context, sInterpolator);
final ViewConfiguration configuration = ViewConfiguration.get(context);
final float density = context.getResources().getDisplayMetrics().density;
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mLeftEdge = new EdgeEffectCompat(context);
mRightEdge = new EdgeEffectCompat(context);
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
mCloseEnough = (int) (CLOSE_ENOUGH * density);
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
@Override
protected void onDetachedFromWindow() {
removeCallbacks(mEndScrollRunnable);
super.onDetachedFromWindow();
}
private void setScrollState(int newState) {
if (mScrollState == newState) {
return;
}
mScrollState = newState;
if (mPageTransformer != null) {
// PageTransformers can do complex things that benefit from hardware layers.
enableLayers(newState != SCROLL_STATE_IDLE);
}
for (OnPageChangeListener eachListener : mOnPageChangeListeners) {
if (eachListener != null) {
eachListener.onPageScrollStateChanged(newState);
}
}
}
private void removeNonDecorViews() {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
removeViewAt(i);
i--;
}
}
}
/**
* Retrieve the current adapter supplying pages.
*
* @return The currently registered PagerAdapter
*/
public PagerAdapter getAdapter() {
return mAdapter;
}
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.registerDataSetObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}
}
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}
}
void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
mAdapterChangeListener = listener;
}
private int getClientWidth() {
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
/**
* Set the currently selected page.
*
* @param item Item index to select
* @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
*/
public void setCurrentItem(int item, boolean smoothScroll) {
mPopulatePending = false;
setCurrentItemInternal(item, smoothScroll, false);
}
public int getCurrentItem() {
return mCurItem;
}
/**
* Set the currently selected page. If the ViewPager has already been through its first
* layout with its current adapter there will be a smooth animated transition between
* the current item and the specified item.
*
* @param item Item index to select
*/
public void setCurrentItem(int item) {
mPopulatePending = false;
setCurrentItemInternal(item, !mFirstLayout, false);
}
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, 0);
}
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
triggerOnPageChangeEvent(item);
requestLayout();
} else {
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destX = 0;
if (curInfo != null) {
final int width = getClientWidth();
destX = (int) (width * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (smoothScroll) {
smoothScrollTo(destX, 0, velocity);
if (dispatchSelected) {
triggerOnPageChangeEvent(item);
}
} else {
if (dispatchSelected) {
triggerOnPageChangeEvent(item);
}
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
/**
* Add a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link OnPageChangeListener}.
*
* @param listener Listener to add
*/
public void addOnPageChangeListener(OnPageChangeListener listener) {
if (!mOnPageChangeListeners.contains(listener)) {
mOnPageChangeListeners.add(listener);
}
}
/**
* Remove a listener that was added with addOnPageChangeListener
* See {@link OnPageChangeListener}.
*
* @param listener Listener to remove
*/
public void removeOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListeners.remove(listener);
}
/**
* Set a {@link PageTransformer} that will be called for each attached page whenever
* the scroll position is changed. This allows the application to apply custom property
* transformations to each page, overriding the default sliding look and feel.
*
* <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
* As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
*
* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
* to be drawn from last to first instead of first to last.
* @param transformer PageTransformer that will modify each page's animation properties
*/
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
mPageTransformer = transformer;
setChildrenDrawingOrderEnabledCompat(hasTransformer);
if (hasTransformer) {
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
} else {
mDrawingOrder = DRAW_ORDER_DEFAULT;
}
if (needsPopulate) populate();
}
void setChildrenDrawingOrderEnabledCompat(boolean enable) {
if (Build.VERSION.SDK_INT >= 7) {
if (mSetChildrenDrawingOrderEnabled == null) {
try {
mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
"setChildrenDrawingOrderEnabled", Boolean.TYPE);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
}
}
try {
mSetChildrenDrawingOrderEnabled.invoke(this, enable);
} catch (Exception e) {
Log.e(TAG, "Error changing children drawing order", e);
}
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
return result;
}
/**
* Set a separate OnPageChangeListener for internal use by the support library.
*
* @param listener Listener to set
* @return The old listener that was set, if any.
*/
OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
OnPageChangeListener oldListener = mInternalPageChangeListener;
mInternalPageChangeListener = listener;
return oldListener;
}
/**
* Returns the number of pages that will be retained to either side of the
* current page in the view hierarchy in an idle state. Defaults to 1.
*
* @return How many pages will be kept offscreen on either side
* @see #setOffscreenPageLimit(int)
*/
public int getOffscreenPageLimit() {
return mOffscreenPageLimit;
}
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
/**
* Return the margin between pages.
*
* @return The size of the margin in pixels
*/
public int getPageMargin() {
return mPageMargin;
}
/**
* Set the margin between pages.
*
* @param marginPixels Distance between adjacent pages in pixels
* @see #getPageMargin()
* @see #setPageMarginDrawable(Drawable)
* @see #setPageMarginDrawable(int)
*/
public void setPageMargin(int marginPixels) {
final int oldMargin = mPageMargin;
mPageMargin = marginPixels;
final int width = getWidth();
recomputeScrollPosition(width, width, marginPixels, oldMargin);
requestLayout();
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param d Drawable to display between pages
*/
public void setPageMarginDrawable(Drawable d) {
mMarginDrawable = d;
if (d != null) refreshDrawableState();
setWillNotDraw(d == null);
invalidate();
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param resId Resource ID of a drawable to display between pages
*/
public void setPageMarginDrawable(int resId) {
setPageMarginDrawable(getContext().getResources().getDrawable(resId));
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mMarginDrawable;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable d = mMarginDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
// We want the duration of the page snap animation to be influenced by the distance that
// the screen has to travel, however, we don't want this duration to be effected in a
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
// of travel has on the overall snap duration.
float distanceInfluenceForSnapDuration(float f) {
f -= 0.5f; // center the values about 0.
f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);
}
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
*/
void smoothScrollTo(int x, int y) {
smoothScrollTo(x, y, 0);
}
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void smoothScrollTo(int x, int y, int velocity) {
if (getChildCount() == 0) {
// Nothing to do.
setScrollingCacheEnabled(false);
return;
}
int sx = getScrollX();
int sy = getScrollY();
int dx = x - sx;
int dy = y - sy;
if (dx == 0 && dy == 0) {
completeScroll(false);
populate();
setScrollState(SCROLL_STATE_IDLE);
return;
}
setScrollingCacheEnabled(true);
setScrollState(SCROLL_STATE_SETTLING);
final int width = getClientWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
mScroller.startScroll(sx, sy, dx, dy, duration);
ViewCompat.postInvalidateOnAnimation(this);
}
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < adapterCount;
int newCurrItem = mCurItem;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
int focusDirection = View.FOCUS_FORWARD;
if (mCurItem != newCurrentItem) {
focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
sortChildDrawingOrder();
return;
}
// Bail now if we are waiting to populate. This is to hold off
// on creating views from the time the user releases their finger to
// fling to a new position until we have finished the scroll to
// that position, avoiding glitches from happening at that point.
if (mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
sortChildDrawingOrder();
return;
}
// Also, don't populate until we are attached to a window. This is to
// avoid trying to populate before we have restored our view hierarchy
// state and conflicting with what is restored.
if (getWindowToken() == null) {
return;
}
mAdapter.startUpdate(this);
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
if (N != mExpectedAdapterCount) {
String resName;
try {
resName = getResources().getResourceName(getId());
} catch (Resources.NotFoundException e) {
resName = Integer.toHexString(getId());
}
throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
" contents without calling PagerAdapter#notifyDataSetChanged!" +
" Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
" Pager id: " + resName +
" Pager class: " + getClass() +
" Problematic adapter: " + mAdapter.getClass());
}
// Locate the currently focused item or add it if needed.
int curIndex = -1;
ItemInfo curItem = null;
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
// Fill 3x the available width or up to the number of offscreen
// pages requested to either side, whichever is larger.
// If we have no current item we have no work to do.
if (curItem != null) {
float extraWidthLeft = 0.f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final int clientWidth = getClientWidth();
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
" view: " + ii.object);
}
itemIndex--;
curIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthLeft += ii.widthFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraWidthLeft += ii.widthFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
float extraWidthRight = curItem.widthFactor;
itemIndex = curIndex + 1;
if (extraWidthRight < 2.f) {
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
final float rightWidthNeeded = clientWidth <= 0 ? 0 :
(float) getPaddingRight() / (float) clientWidth + 2.f;
for (int pos = mCurItem + 1; pos < N; pos++) {
if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
" view: " + ii.object);
}
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthRight += ii.widthFactor;
itemIndex++;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex);
itemIndex++;
extraWidthRight += ii.widthFactor;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
}
}
calculatePageOffsets(curItem, curIndex, oldCurInfo);
}
if (DEBUG) {
Log.i(TAG, "Current page list:");
for (int i = 0; i < mItems.size(); i++) {
Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
}
}
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
mAdapter.finishUpdate(this);
// Check width measurement of current pages and drawing sort order.
// Update LayoutParams as needed.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.childIndex = i;
if (!lp.isDecor && lp.widthFactor == 0.f) {
// 0 means requery the adapter for this, it doesn't have a valid width.
final ItemInfo ii = infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}
}
}
sortChildDrawingOrder();
if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
if (ii == null || ii.position != mCurItem) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(focusDirection)) {
break;
}
}
}
}
}
}
private void sortChildDrawingOrder() {
if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
if (mDrawingOrderedChildren == null) {
mDrawingOrderedChildren = new ArrayList<View>();
} else {
mDrawingOrderedChildren.clear();
}
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
mDrawingOrderedChildren.add(child);
}
Collections.sort(mDrawingOrderedChildren, sPositionComparator);
}
}
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
final int N = mAdapter.getCount();
final int width = getClientWidth();
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
// Fix up offsets for later layout.
if (oldCurInfo != null) {
final int oldCurPosition = oldCurInfo.position;
// Base offsets off of oldCurInfo.
if (oldCurPosition < curItem.position) {
int itemIndex = 0;
ItemInfo ii = null;
float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
for (int pos = oldCurPosition + 1;
pos <= curItem.position && itemIndex < mItems.size(); pos++) {
ii = mItems.get(itemIndex);
while (pos > ii.position && itemIndex < mItems.size() - 1) {
itemIndex++;
ii = mItems.get(itemIndex);
}
while (pos < ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset += mAdapter.getPageWidth(pos) + marginOffset;
pos++;
}
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
} else if (oldCurPosition > curItem.position) {
int itemIndex = mItems.size() - 1;
ItemInfo ii = null;
float offset = oldCurInfo.offset;
for (int pos = oldCurPosition - 1;
pos >= curItem.position && itemIndex >= 0; pos--) {
ii = mItems.get(itemIndex);
while (pos < ii.position && itemIndex > 0) {
itemIndex--;
ii = mItems.get(itemIndex);
}
while (pos > ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset -= mAdapter.getPageWidth(pos) + marginOffset;
pos--;
}
offset -= ii.widthFactor + marginOffset;
ii.offset = offset;
}
}
}
// Base all offsets off of curItem.
final int itemCount = mItems.size();
float offset = curItem.offset;
int pos = curItem.position - 1;
mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
mLastOffset = curItem.position == N - 1 ?
curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
// Previous pages
for (int i = curIndex - 1; i >= 0; i--, pos--) {
final ItemInfo ii = mItems.get(i);
while (pos > ii.position) {
offset -= mAdapter.getPageWidth(pos--) + marginOffset;
}
offset -= ii.widthFactor + marginOffset;
ii.offset = offset;
if (ii.position == 0) mFirstOffset = offset;
}
offset = curItem.offset + curItem.widthFactor + marginOffset;
pos = curItem.position + 1;
// Next pages
for (int i = curIndex + 1; i < itemCount; i++, pos++) {
final ItemInfo ii = mItems.get(i);
while (pos < ii.position) {
offset += mAdapter.getPageWidth(pos++) + marginOffset;
}
if (ii.position == N - 1) {
mLastOffset = offset + ii.widthFactor - 1;
}
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
mNeedCalculatePageOffsets = false;
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);
} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
final LayoutParams lp = (LayoutParams) params;
lp.isDecor |= child instanceof Decor;
if (mInLayout) {
if (lp != null && lp.isDecor) {
throw new IllegalStateException("Cannot add pager decor view during layout");
}
lp.needsMeasure = true;
addViewInLayout(child, index, params);
} else {
super.addView(child, index, params);
}
if (USE_CACHE) {
if (child.getVisibility() != GONE) {
child.setDrawingCacheEnabled(mScrollingCacheEnabled);
} else {
child.setDrawingCacheEnabled(false);
}
}
}
@Override
public void removeView(View view) {
if (mInLayout) {
removeViewInLayout(view);
} else {
super.removeView(view);
}
}
ItemInfo infoForChild(View child) {
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (mAdapter.isViewFromObject(child, ii.object)) {
return ii;
}
}
return null;
}
ItemInfo infoForAnyChild(View child) {
ViewParent parent;
while ((parent = child.getParent()) != this) {
if (parent == null || !(parent instanceof View)) {
return null;
}
child = (View) parent;
}
return infoForChild(child);
}
ItemInfo infoForPosition(int position) {
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (ii.position == position) {
return ii;
}
}
return null;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// For simple implementation, our internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
/*
* Make sure all children have been properly measured. Decor views first.
* Right now we cheat and make this less complicated by assuming decor
* views won't intersect. We will pin to edges based on gravity.
*/
int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp != null && lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
int widthMode = MeasureSpec.AT_MOST;
int heightMode = MeasureSpec.AT_MOST;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
if (consumeVertical) {
widthMode = MeasureSpec.EXACTLY;
} else if (consumeHorizontal) {
heightMode = MeasureSpec.EXACTLY;
}
int widthSize = childWidthSize;
int heightSize = childHeightSize;
if (lp.width != LayoutParams.WRAP_CONTENT) {
widthMode = MeasureSpec.EXACTLY;
if (lp.width != LayoutParams.FILL_PARENT) {
widthSize = lp.width;
}
}
if (lp.height != LayoutParams.WRAP_CONTENT) {
heightMode = MeasureSpec.EXACTLY;
if (lp.height != LayoutParams.FILL_PARENT) {
heightSize = lp.height;
}
}
final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
child.measure(widthSpec, heightSpec);
if (consumeVertical) {
childHeightSize -= child.getMeasuredHeight();
} else if (consumeHorizontal) {
childWidthSize -= child.getMeasuredWidth();
}
}
}
}
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+ ": " + mChildWidthMeasureSpec);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
child.measure(widthSpec, mChildHeightMeasureSpec);
}
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Make sure scroll position is set correctly.
if (w != oldw) {
recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
}
}
private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
if (oldWidth > 0 && !mItems.isEmpty()) {
final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
+ oldMargin;
final int xpos = getScrollX();
final float pageOffset = (float) xpos / oldWidthWithMargin;
final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
scrollTo(newOffsetPixels, getScrollY());
if (!mScroller.isFinished()) {
// We now return to your regularly scheduled scroll, already in progress.
final int newDuration = mScroller.getDuration() - mScroller.timePassed();
ItemInfo targetInfo = infoForPosition(mCurItem);
mScroller.startScroll(newOffsetPixels, 0,
(int) (targetInfo.offset * width), 0, newDuration);
}
} else {
final ItemInfo ii = infoForPosition(mCurItem);
final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
final int scrollPos = (int) (scrollOffset *
(width - getPaddingLeft() - getPaddingRight()));
if (scrollPos != getScrollX()) {
completeScroll(false);
scrollTo(scrollPos, getScrollY());
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
int width = r - l;
int height = b - t;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
final int scrollX = getScrollX();
int decorCount = 0;
// First pass - decor views. We need to do this in two passes so that
// we have the proper offsets for non-decor views later.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childLeft = 0;
int childTop = 0;
if (lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getMeasuredWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidth();
paddingRight += child.getMeasuredWidth();
break;
}
switch (vgrav) {
default:
childTop = paddingTop;
break;
case Gravity.TOP:
childTop = paddingTop;
paddingTop += child.getMeasuredHeight();
break;
case Gravity.CENTER_VERTICAL:
childTop = Math.max((height - child.getMeasuredHeight()) / 2,
paddingTop);
break;
case Gravity.BOTTOM:
childTop = height - paddingBottom - child.getMeasuredHeight();
paddingBottom += child.getMeasuredHeight();
break;
}
childLeft += scrollX;
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
decorCount++;
}
}
}
final int childWidth = width - paddingLeft - paddingRight;
// Page views. Do this once we have the right padding offsets from above.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
ItemInfo ii;
if (!lp.isDecor && (ii = infoForChild(child)) != null) {
int loff = (int) (childWidth * ii.offset);
int childLeft = paddingLeft + loff;
int childTop = paddingTop;
if (lp.needsMeasure) {
// This was added during layout and needs measurement.
// Do it now that we know what we're working with.
lp.needsMeasure = false;
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidth * lp.widthFactor),
MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(
height - paddingTop - paddingBottom,
MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ "x" + child.getMeasuredHeight());
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
mTopPageBounds = paddingTop;
mBottomPageBounds = height - paddingBottom;
mDecorChildCount = decorCount;
if (mFirstLayout) {
scrollToItem(mCurItem, false, 0, false);
}
mFirstLayout = false;
}
@Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
// Done with scroll, clean up state.
completeScroll(true);
}
private boolean pageScrolled(int xpos) {
if (mItems.size() == 0) {
mCalledSuper = false;
onPageScrolled(0, 0, 0);
if (!mCalledSuper) {
throw new IllegalStateException(
"onPageScrolled did not call superclass implementation");
}
return false;
}
final ItemInfo ii = infoForCurrentScrollPosition();
final int width = getClientWidth();
final int widthWithMargin = width + mPageMargin;
final float marginOffset = (float) mPageMargin / width;
final int currentPage = ii.position;
final float pageOffset = (((float) xpos / width) - ii.offset) /
(ii.widthFactor + marginOffset);
final int offsetPixels = (int) (pageOffset * widthWithMargin);
mCalledSuper = false;
onPageScrolled(currentPage, pageOffset, offsetPixels);
if (!mCalledSuper) {
throw new IllegalStateException(
"onPageScrolled did not call superclass implementation");
}
return true;
}
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
* If you override this method you must call through to the superclass implementation
* (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
* returns.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param offset Value from [0, 1) indicating the offset from the page at position.
* @param offsetPixels Value in pixels indicating the offset from position.
*/
protected void onPageScrolled(int position, float offset, int offsetPixels) {
// Offset any decor views if needed - keep them on-screen at all times.
if (mDecorChildCount > 0) {
final int scrollX = getScrollX();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
final int width = getWidth();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) continue;
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int childLeft = 0;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidth();
paddingRight += child.getMeasuredWidth();
break;
}
childLeft += scrollX;
final int childOffset = childLeft - child.getLeft();
if (childOffset != 0) {
child.offsetLeftAndRight(childOffset);
}
}
}
for (OnPageChangeListener eachListener : mOnPageChangeListeners) {
if (eachListener != null) {
eachListener.onPageScrolled(position, offset, offsetPixels);
}
}
if (mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
}
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
mCalledSuper = true;
}
private void completeScroll(boolean postEvents) {
boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
if (needPopulate) {
// Done with scroll, no longer want to cache view drawing.
setScrollingCacheEnabled(false);
mScroller.abortAnimation();
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
}
}
mPopulatePending = false;
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (ii.scrolling) {
needPopulate = true;
ii.scrolling = false;
}
}
if (needPopulate) {
if (postEvents) {
ViewCompat.postOnAnimation(this, mEndScrollRunnable);
} else {
mEndScrollRunnable.run();
}
}
}
private boolean isGutterDrag(float x, float dx) {
return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
}
private void enableLayers(boolean enable) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final int layerType = enable ?
ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
ViewCompat.setLayerType(getChildAt(i), layerType, null);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
// Always take care of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the drag.
if (DEBUG) Log.v(TAG, "Intercept done!");
mIsBeingDragged = false;
mIsUnableToDrag = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
return false;
}
// Nothing more to do here if we have decided whether or not we
// are dragging.
if (action != MotionEvent.ACTION_DOWN) {
if (mIsBeingDragged) {
if (DEBUG) Log.v(TAG, "Intercept returning true!");
return true;
}
if (mIsUnableToDrag) {
if (DEBUG) Log.v(TAG, "Intercept returning false!");
return false;
}
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float dx = x - mLastMotionX;
final float xDiff = Math.abs(dx);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mInitialMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
canScroll(this, false, (int) dx, (int) x, (int) y)) {
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
mIsUnableToDrag = true;
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsUnableToDrag = false;
mScroller.computeScrollOffset();
if (mScrollState == SCROLL_STATE_SETTLING &&
Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
// Let the user 'catch' the pager as it animates.
mScroller.abortAnimation();
mPopulatePending = false;
populate();
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
} else {
completeScroll(false);
mIsBeingDragged = false;
}
if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ " mIsBeingDragged=" + mIsBeingDragged
+ "mIsUnableToDrag=" + mIsUnableToDrag);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mFakeDragging) {
// A fake drag is in progress already, ignore this real one
// but still eat the touch events.
// (It is likely that the user is multi-touching the screen.)
return true;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
if (mAdapter == null || mAdapter.getCount() == 0) {
// Nothing to present or scroll; nothing to touch.
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
boolean needsInvalidate = false;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mScroller.abortAnimation();
mPopulatePending = false;
populate();
// Remember where the motion event started
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
break;
}
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG)
Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (xDiff > mTouchSlop && xDiff > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = MotionEventCompat.findPointerIndex(
ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
needsInvalidate |= performDrag(x);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;
final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
scrollToItem(mCurItem, true, 0, false);
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int index = MotionEventCompat.getActionIndex(ev);
final float x = MotionEventCompat.getX(ev, index);
mLastMotionX = x;
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = MotionEventCompat.getX(ev,
MotionEventCompat.findPointerIndex(ev, mActivePointerId));
break;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
return true;
}
private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
private boolean performDrag(float x) {
boolean needsInvalidate = false;
final float deltaX = mLastMotionX - x;
mLastMotionX = x;
float oldScrollX = getScrollX();
float scrollX = oldScrollX + deltaX;
final int width = getClientWidth();
float leftBound = width * mFirstOffset;
float rightBound = width * mLastOffset;
boolean leftAbsolute = true;
boolean rightAbsolute = true;
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
if (firstItem.position != 0) {
leftAbsolute = false;
leftBound = firstItem.offset * width;
}
if (lastItem.position != mAdapter.getCount() - 1) {
rightAbsolute = false;
rightBound = lastItem.offset * width;
}
if (scrollX < leftBound) {
if (leftAbsolute) {
float over = leftBound - scrollX;
needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
}
scrollX = leftBound;
} else if (scrollX > rightBound) {
if (rightAbsolute) {
float over = scrollX - rightBound;
needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
}
scrollX = rightBound;
}
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
pageScrolled((int) scrollX);
return needsInvalidate;
}
/**
* @return Info about the page at the current scroll position.
* This can be synthetic for a missing middle page; the 'object' field can be null.
*/
private ItemInfo infoForCurrentScrollPosition() {
final int width = getClientWidth();
final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
int lastPos = -1;
float lastOffset = 0.f;
float lastWidth = 0.f;
boolean first = true;
ItemInfo lastItem = null;
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
float offset;
if (!first && ii.position != lastPos + 1) {
// Create a synthetic item for a missing page.
ii = mTempItem;
ii.offset = lastOffset + lastWidth + marginOffset;
ii.position = lastPos + 1;
ii.widthFactor = mAdapter.getPageWidth(ii.position);
i--;
}
offset = ii.offset;
final float leftBound = offset;
final float rightBound = offset + ii.widthFactor + marginOffset;
if (first || scrollOffset >= leftBound) {
if (scrollOffset < rightBound || i == mItems.size() - 1) {
return ii;
}
} else {
return lastItem;
}
first = false;
lastPos = ii.position;
lastOffset = offset;
lastWidth = ii.widthFactor;
lastItem = ii;
}
return lastItem;
}
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = (int) (currentPage + pageOffset + truncator);
}
if (mItems.size() > 0) {
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
// Only let the user target pages we have items for
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
}
return targetPage;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
boolean needsInvalidate = false;
final int overScrollMode = ViewCompat.getOverScrollMode(this);
if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
(overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
mAdapter != null && mAdapter.getCount() > 1)) {
if (!mLeftEdge.isFinished()) {
final int restoreCount = canvas.save();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
final int width = getWidth();
canvas.rotate(270);
canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
mLeftEdge.setSize(height, width);
needsInvalidate |= mLeftEdge.draw(canvas);
canvas.restoreToCount(restoreCount);
}
if (!mRightEdge.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
canvas.rotate(90);
canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
mRightEdge.setSize(height, width);
needsInvalidate |= mRightEdge.draw(canvas);
canvas.restoreToCount(restoreCount);
}
} else {
mLeftEdge.finish();
mRightEdge.finish();
}
if (needsInvalidate) {
// Keep animating
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the margin drawable between pages if needed.
if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
final int scrollX = getScrollX();
final int width = getWidth();
final float marginOffset = (float) mPageMargin / width;
int itemIndex = 0;
ItemInfo ii = mItems.get(0);
float offset = ii.offset;
final int itemCount = mItems.size();
final int firstPos = ii.position;
final int lastPos = mItems.get(itemCount - 1).position;
for (int pos = firstPos; pos < lastPos; pos++) {
while (pos > ii.position && itemIndex < itemCount) {
ii = mItems.get(++itemIndex);
}
float drawAt;
if (pos == ii.position) {
drawAt = (ii.offset + ii.widthFactor) * width;
offset = ii.offset + ii.widthFactor + marginOffset;
} else {
float widthFactor = mAdapter.getPageWidth(pos);
drawAt = (offset + widthFactor) * width;
offset += widthFactor + marginOffset;
}
if (drawAt + mPageMargin > scrollX) {
mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
(int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
mMarginDrawable.draw(canvas);
}
if (drawAt > scrollX + width) {
break; // No more visible, no sense in continuing
}
}
}
}
/**
* Start a fake drag of the pager.
*
* <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
* with the touch scrolling of another view, while still letting the ViewPager
* control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
* Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
* {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
*
* <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
* is already in progress, this method will return false.
*
* @return true if the fake drag began successfully, false if it could not be started.
*
* @see #fakeDragBy(float)
* @see #endFakeDrag()
*/
public boolean beginFakeDrag() {
if (mIsBeingDragged) {
return false;
}
mFakeDragging = true;
setScrollState(SCROLL_STATE_DRAGGING);
mInitialMotionX = mLastMotionX = 0;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
final long time = SystemClock.uptimeMillis();
final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
mVelocityTracker.addMovement(ev);
ev.recycle();
mFakeDragBeginTime = time;
return true;
}
/**
* End a fake drag of the pager.
*
* @see #beginFakeDrag()
* @see #fakeDragBy(float)
*/
public void endFakeDrag() {
if (!mFakeDragging) {
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
}
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;
final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
endDrag();
mFakeDragging = false;
}
/**
* Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
*
* @param xOffset Offset in pixels to drag by.
* @see #beginFakeDrag()
* @see #endFakeDrag()
*/
public void fakeDragBy(float xOffset) {
if (!mFakeDragging) {
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
}
mLastMotionX += xOffset;
float oldScrollX = getScrollX();
float scrollX = oldScrollX - xOffset;
final int width = getClientWidth();
float leftBound = width * mFirstOffset;
float rightBound = width * mLastOffset;
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
if (firstItem.position != 0) {
leftBound = firstItem.offset * width;
}
if (lastItem.position != mAdapter.getCount() - 1) {
rightBound = lastItem.offset * width;
}
if (scrollX < leftBound) {
scrollX = leftBound;
} else if (scrollX > rightBound) {
scrollX = rightBound;
}
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
pageScrolled((int) scrollX);
// Synthesize an event for the VelocityTracker.
final long time = SystemClock.uptimeMillis();
final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
mLastMotionX, 0, 0);
mVelocityTracker.addMovement(ev);
ev.recycle();
}
/**
* Returns true if a fake drag is in progress.
*
* @return true if currently in a fake drag, false otherwise.
*
* @see #beginFakeDrag()
* @see #fakeDragBy(float)
* @see #endFakeDrag()
*/
public boolean isFakeDragging() {
return mFakeDragging;
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
private void endDrag() {
mIsBeingDragged = false;
mIsUnableToDrag = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private void setScrollingCacheEnabled(boolean enabled) {
if (mScrollingCacheEnabled != enabled) {
mScrollingCacheEnabled = enabled;
if (USE_CACHE) {
final int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.setDrawingCacheEnabled(enabled);
}
}
}
}
}
public boolean canScrollHorizontally(int direction) {
if (mAdapter == null) {
return false;
}
final int width = getClientWidth();
final int scrollX = getScrollX();
if (direction < 0) {
return (scrollX > (int) (width * mFirstOffset));
} else if (direction > 0) {
return (scrollX < (int) (width * mLastOffset));
} else {
return false;
}
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
* or just its children (false).
* @param dx Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
canScroll(child, true, dx, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && ViewCompat.canScrollHorizontally(v, -dx);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Let the focused view and/or our descendants get the key first
return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
/**
* You can call this function yourself to have the scroll view perform
* scrolling from a key event, just as if the event had been dispatched to
* it by the view hierarchy.
*
* @param event The key event to execute.
* @return Return true if the event was handled, else false.
*/
public boolean executeKeyEvent(KeyEvent event) {
boolean handled = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
handled = arrowScroll(FOCUS_LEFT);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled = arrowScroll(FOCUS_RIGHT);
break;
case KeyEvent.KEYCODE_TAB:
if (Build.VERSION.SDK_INT >= 11) {
// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
// before Android 3.0. Ignore the tab key on those devices.
// if (KeyEventCompat.hasNoModifiers(event)) {
// handled = arrowScroll(FOCUS_FORWARD);
// } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
// handled = arrowScroll(FOCUS_BACKWARD);
// }
}
break;
}
}
return handled;
}
public boolean arrowScroll(int direction) {
View currentFocused = findFocus();
if (currentFocused == this) {
currentFocused = null;
} else if (currentFocused != null) {
boolean isChild = false;
for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
parent = parent.getParent()) {
if (parent == this) {
isChild = true;
break;
}
}
if (!isChild) {
// This would cause the focus search down below to fail in fun ways.
final StringBuilder sb = new StringBuilder();
sb.append(currentFocused.getClass().getSimpleName());
for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
parent = parent.getParent()) {
sb.append(" => ").append(parent.getClass().getSimpleName());
}
Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
"current focused view " + sb.toString());
currentFocused = null;
}
}
boolean handled = false;
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
direction);
if (nextFocused != null && nextFocused != currentFocused) {
if (direction == View.FOCUS_LEFT) {
// If there is nothing to the left, or this is causing us to
// jump to the right, then what we really want to do is page left.
final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
if (currentFocused != null && nextLeft >= currLeft) {
handled = pageLeft();
} else {
handled = nextFocused.requestFocus();
}
} else if (direction == View.FOCUS_RIGHT) {
// If there is nothing to the right, or this is causing us to
// jump to the left, then what we really want to do is page right.
final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
if (currentFocused != null && nextLeft <= currLeft) {
handled = pageRight();
} else {
handled = nextFocused.requestFocus();
}
}
} else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
// Trying to move left and nothing there; try to page.
handled = pageLeft();
} else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
// Trying to move right and nothing there; try to page.
handled = pageRight();
}
if (handled) {
playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
}
return handled;
}
private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
if (outRect == null) {
outRect = new Rect();
}
if (child == null) {
outRect.set(0, 0, 0, 0);
return outRect;
}
outRect.left = child.getLeft();
outRect.right = child.getRight();
outRect.top = child.getTop();
outRect.bottom = child.getBottom();
ViewParent parent = child.getParent();
while (parent instanceof ViewGroup && parent != this) {
final ViewGroup group = (ViewGroup) parent;
outRect.left += group.getLeft();
outRect.right += group.getRight();
outRect.top += group.getTop();
outRect.bottom += group.getBottom();
parent = group.getParent();
}
return outRect;
}
boolean pageLeft() {
if (mCurItem > 0) {
setCurrentItem(mCurItem - 1, true);
return true;
}
return false;
}
boolean pageRight() {
if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
setCurrentItem(mCurItem + 1, true);
return true;
}
return false;
}
/**
* We only want the current page that is being shown to be focusable.
*/
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == VISIBLE) {
ItemInfo ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
child.addFocusables(views, direction, focusableMode);
}
}
}
}
// we add ourselves (if focusable) in all cases except for when we are
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if (
descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
// No focusable descendants
(focusableCount == views.size())) {
// Note that we can't call the superclass here, because it will
// add all views in. So we need to do the same thing View does.
if (!isFocusable()) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
isInTouchMode() && !isFocusableInTouchMode()) {
return;
}
if (views != null) {
views.add(this);
}
}
}
/**
* We only want the current page that is being shown to be touchable.
*/
@Override
public void addTouchables(ArrayList<View> views) {
// Note that we don't call super.addTouchables(), which means that
// we don't call View.addTouchables(). This is okay because a ViewPager
// is itself not touchable.
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == VISIBLE) {
ItemInfo ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
child.addTouchables(views);
}
}
}
}
/**
* We only want the current page that is being shown to be focusable.
*/
@Override
protected boolean onRequestFocusInDescendants(int direction,
Rect previouslyFocusedRect) {
int index;
int increment;
int end;
int count = getChildCount();
if ((direction & FOCUS_FORWARD) != 0) {
index = 0;
increment = 1;
end = count;
} else {
index = count - 1;
increment = -1;
end = -1;
}
for (int i = index; i != end; i += increment) {
View child = getChildAt(i);
if (child.getVisibility() == VISIBLE) {
ItemInfo ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(direction, previouslyFocusedRect)) {
return true;
}
}
}
}
return false;
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
// Dispatch scroll events from this ViewPager.
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
return super.dispatchPopulateAccessibilityEvent(event);
}
// Dispatch all other accessibility events from the current page.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == VISIBLE) {
final ItemInfo ii = infoForChild(child);
if (ii != null && ii.position == mCurItem &&
child.dispatchPopulateAccessibilityEvent(event)) {
return true;
}
}
}
return false;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return generateDefaultLayoutParams();
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/**
* Callback interface for responding to changing state of the selected page.
*/
public interface OnPageChangeListener {
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
/**
* This method will be invoked when a new page becomes selected. Animation is not
* necessarily complete.
*
* @param position Position index of the new selected page.
*/
void onPageSelected(int position);
/**
* Called when the scroll state changes. Useful for discovering when the user
* begins dragging, when the pager is automatically settling to the current page,
* or when it is fully stopped/idle.
*
* @param state The new scroll state.
* @see ViewPagerEx#SCROLL_STATE_IDLE
* @see ViewPagerEx#SCROLL_STATE_DRAGGING
* @see ViewPagerEx#SCROLL_STATE_SETTLING
*/
void onPageScrollStateChanged(int state);
}
/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the page views using animation properties.
*
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions will
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(View page, float position);
}
/**
* Used internally to monitor when adapters are switched.
*/
interface OnAdapterChangeListener {
void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
}
/**
* Used internally to tag special types of child views that should be added as
* pager decorations by default.
*/
interface Decor {
}
static class ItemInfo {
Object object;
int position;
boolean scrolling;
float widthFactor;
float offset;
}
/**
* Simple implementation of the {@link OnPageChangeListener} interface with stub
* implementations of each method. Extend this if you do not intend to override
* every method of {@link OnPageChangeListener}.
*/
public static class SimpleOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This space for rent
}
@Override
public void onPageSelected(int position) {
// This space for rent
}
@Override
public void onPageScrollStateChanged(int state) {
// This space for rent
}
}
/**
* This is the persistent state that is saved by ViewPager. Only needed
* if you are creating a sublass of ViewPager that must save its own
* state, in which case it should implement a subclass of this which
* contains that state.
*/
public static class SavedState extends BaseSavedState {
public static final Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
int position;
Parcelable adapterState;
ClassLoader loader;
public SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel in, ClassLoader loader) {
super(in);
if (loader == null) {
loader = getClass().getClassLoader();
}
position = in.readInt();
adapterState = in.readParcelable(loader);
this.loader = loader;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(position);
out.writeParcelable(adapterState, flags);
}
@Override
public String toString() {
return "FragmentPager.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " position=" + position + "}";
}
}
/**
* Layout parameters that should be supplied for views added to a
* ViewPager.
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* true if this view is a decoration on the pager itself and not
* a view supplied by the adapter.
*/
public boolean isDecor;
/**
* Gravity setting for use on decor views only:
* Where to position the view page within the overall ViewPager
* container; constants are defined in {@link Gravity}.
*/
public int gravity;
/**
* Width as a 0-1 multiplier of the measured pager width
*/
float widthFactor = 0.f;
/**
* true if this view was added during layout and needs to be measured
* before being positioned.
*/
boolean needsMeasure;
/**
* Adapter position this view is for if !isDecor
*/
int position;
/**
* Current child index within the ViewPager that this view occupies
*/
int childIndex;
public LayoutParams() {
super(FILL_PARENT, FILL_PARENT);
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
gravity = a.getInteger(0, Gravity.TOP);
a.recycle();
}
}
static class ViewPositionComparator implements Comparator<View> {
@Override
public int compare(View lhs, View rhs) {
final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
if (llp.isDecor != rlp.isDecor) {
return llp.isDecor ? 1 : -1;
}
return llp.position - rlp.position;
}
}
class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(ViewPagerEx.class.getName());
final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
recordCompat.setScrollable(canScroll());
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
&& mAdapter != null) {
recordCompat.setItemCount(mAdapter.getCount());
recordCompat.setFromIndex(mCurItem);
recordCompat.setToIndex(mCurItem);
}
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(ViewPagerEx.class.getName());
info.setScrollable(canScroll());
if (canScrollHorizontally(1)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
}
if (canScrollHorizontally(-1)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
}
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (super.performAccessibilityAction(host, action, args)) {
return true;
}
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
if (canScrollHorizontally(1)) {
setCurrentItem(mCurItem + 1);
return true;
}
}
return false;
case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
if (canScrollHorizontally(-1)) {
setCurrentItem(mCurItem - 1);
return true;
}
}
return false;
}
return false;
}
private boolean canScroll() {
return (mAdapter != null) && (mAdapter.getCount() > 1);
}
}
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class ZoomInTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
final float scale = position < 0 ? position + 1f : Math.abs(1f - position);
ViewHelper.setScaleX(view, scale);
ViewHelper.setScaleY(view, scale);
ViewHelper.setPivotX(view, view.getWidth() * 0.5f);
ViewHelper.setPivotY(view, view.getHeight() * 0.5f);
ViewHelper.setAlpha(view, position < -1f || position > 1f ? 0f : 1f - (scale - 1f));
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class ZoomOutSlideTransformer extends BaseTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
@Override
protected void onTransform(View view, float position) {
if (position >= -1 || position <= 1) {
// Modify the default slide transition to shrink the page as well
final float height = view.getHeight();
final float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
final float vertMargin = height * (1 - scaleFactor) / 2;
final float horzMargin = view.getWidth() * (1 - scaleFactor) / 2;
// Center vertically
ViewHelper.setPivotY(view, 0.5f * height);
if (position < 0) {
ViewHelper.setTranslationX(view, horzMargin - vertMargin / 2);
} else {
ViewHelper.setTranslationX(view, -horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
ViewHelper.setScaleX(view, scaleFactor);
ViewHelper.setScaleY(view, scaleFactor);
// Fade the page relative to its size.
ViewHelper.setAlpha(view, MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
}
}
}
import android.view.View;
import com.nineoldandroids.view.ViewHelper;
public class ZoomOutTransformer extends BaseTransformer {
@Override
protected void onTransform(View view, float position) {
final float scale = 1f + Math.abs(position);
ViewHelper.setScaleX(view, scale);
ViewHelper.setScaleY(view, scale);
ViewHelper.setPivotX(view, view.getWidth() * 0.5f);
ViewHelper.setPivotY(view, view.getWidth() * 0.5f);
ViewHelper.setAlpha(view, position < -1f || position > 1f ? 0f : 1f - (scale - 1f));
if (position < -0.9) {
//-0.9 to prevent a small bug
ViewHelper.setTranslationX(view, view.getWidth() * position);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment