Last active
July 26, 2019 13:31
-
-
Save erickogi/7a04053a5b5e03eff38a9eab0ef00adc to your computer and use it in GitHub Desktop.
Slider codes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.view.View; | |
| public class DefaultTransformer extends BaseTransformer { | |
| @Override | |
| protected void onTransform(View view, float position) { | |
| } | |
| @Override | |
| public boolean isPagingEnabled() { | |
| return true; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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