Java tutorial
/* * Copyright (C) 2016 The Edgar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.edgar.banner; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v4.content.ContextCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.Scroller; import com.edgar.banner.indicator.BannerIndicator; import com.edgar.banner.indicator.IndicatorAttributeSet; import com.edgar.banner.indicator.IndicatorFactory; import com.edgar.banner.indicator.IndicatorGravity; import com.edgar.banner.indicator.IndicatorStyle; import com.edgar.transformers.PageTransformerFactory; import com.edgar.transformers.TransformerType; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * Created by edgar on 2016/1/6. * Banner pager * : ,indicator,??, * ?indicator */ public class BannerPagerView extends FrameLayout { public interface OnBannerClickListener { void onBannerClick(BannerItem bannerItem); } private static final String TAG = BannerPagerView.class.getSimpleName(); private final LayoutParams mIndicatorParams = new LayoutParams(MATCH_PARENT, WRAP_CONTENT); private static final int DEFAULT_INIT_POSITION = 0; private static final long DEFAULT_DELAY_TIME = 8 * 10_00; private static final int AUTO_PLAY_DISABLE = 0x00000020; private static final int AUTO_PLAY_ENABLE = 0x00000000; private static final int AUTO_PLAY_MARK = 0x00000020; private static final int INVALID_POINTER = -1; private static final int MAX_TOUCH_SLOP = 10; /** * Banner?,4?,?2?,???. */ private static final int MI_BANNER_COUNT = 4; private float mLastMotionX; private float mLastMotionY; private int mTouchSlop = MAX_TOUCH_SLOP; private int mActivePointerId = INVALID_POINTER; /** * Banner list */ private final List<BannerItem> mBannerList = new ArrayList<>(); private BannerIndicator mBannerIndicator; private View mIndicatorView; private LoopViewPager mViewPage; private BannerPageAdapter mPageAdapter; private long mIntervalTime = DEFAULT_DELAY_TIME; //Banner flags private int mBannerFlags; private int mBannerPageInitPosition = DEFAULT_INIT_POSITION; private boolean mTouchDown; private final BannerPageListener mCarousePageListener = new BannerPageListener(); private BannerPageViewAdapter mBannerPageAdapter; private ImageLoader mImageLoader; private Scroller mBannerScroller; private Field mScrollerField; private List<ViewPager.OnPageChangeListener> mOnPageChangeListeners; private Drawable mIndicatorBackground; private Drawable mUnSelectedDrawable; private Drawable mSelectedDrawable; private int mIndicatorStyle = IndicatorStyle.CIRCLE_INDICATOR; private int mIndicatorGravity = IndicatorGravity.CENTER; private int mPointPadding; private OnBannerClickListener mOnBannerClickListener; private final View.OnClickListener mBannerClickListener = new OnClickListener() { @Override public void onClick(View v) { if (mOnBannerClickListener != null) { mOnBannerClickListener.onBannerClick((BannerItem) v.getTag()); } } }; private static final int LOOPER = 0x1000; private final Handler mBannerLooperHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case LOOPER: if (isEnableAutoPlay() && !mTouchDown) { updateViewPage(); sendEmptyMessageDelayed(LOOPER, mIntervalTime); } else { removeMessages(LOOPER); } Log.e(TAG, "Banner looper"); break; } } private void updateViewPage() { int currentItem = mViewPage.getCurrentItem(); int newItem = currentItem + 1; mViewPage.setCurrentItem(newItem, true); } }; public BannerPagerView(Context context) { super(context); init(null, 0); } public BannerPagerView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs, 0); } public BannerPagerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs, defStyle); } private void init(AttributeSet attrs, int defStyle) { Context context = getContext(); Resources resource = context.getResources(); LayoutInflater.from(getContext()).inflate(R.layout.banner_layout, this, true); mViewPage = (LoopViewPager) findViewById(R.id.carouse_viewpager); setBannerScroller(new BannerScroller(getContext())); mViewPage.setOverScrollMode(View.OVER_SCROLL_NEVER); mViewPage.setOnPageChangeListener(mCarousePageListener); mBannerPageAdapter = new DefaultBannerPageViewAdapter(); mIndicatorParams.gravity = Gravity.BOTTOM; mPointPadding = resource.getDimensionPixelOffset(R.dimen.point_margin); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BannerPagerView, defStyle, 0); int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); if (attr == R.styleable.BannerPagerView_bannerAnimation) { int transformerType = a.getInt(attr, 0); setBannerPageTransformer(true, TransformerType.convert(transformerType)); } else if (attr == R.styleable.BannerPagerView_enableAutoPlayer) { setEnableAutoPlay(a.getBoolean(attr, false)); } else if (attr == R.styleable.BannerPagerView_indicatorStyle) { mIndicatorStyle = a.getInt(attr, IndicatorStyle.CIRCLE_INDICATOR); } else if (attr == R.styleable.BannerPagerView_indicatorLayout) { int resId = a.getResourceId(attr, 0); if (resId != 0) { View indicatorView = LayoutInflater.from(context).inflate(resId, this, false); if (!(indicatorView instanceof BannerIndicator)) { throw new IllegalArgumentException("Your indicator must implements BannerIndicator."); } mIndicatorView = indicatorView; mBannerIndicator = (BannerIndicator) indicatorView; addView(mIndicatorView); } } else if (attr == R.styleable.BannerPagerView_unSelectDrawable) { mUnSelectedDrawable = a.getDrawable(attr); } else if (attr == R.styleable.BannerPagerView_selectedDrawable) { mSelectedDrawable = a.getDrawable(attr); } else if (attr == R.styleable.BannerPagerView_indicatorGravity) { mIndicatorGravity = a.getInt(attr, Gravity.CENTER); } else if (attr == R.styleable.BannerPagerView_pointPadding) { mPointPadding = a.getDimensionPixelOffset(attr, mPointPadding); } else if (attr == R.styleable.BannerPagerView_indicatorBackground) { mIndicatorBackground = a.getDrawable(attr); } } if (mUnSelectedDrawable == null) { mUnSelectedDrawable = ContextCompat.getDrawable(context, R.drawable.def_circle_normal_background); } if (mSelectedDrawable == null) { mSelectedDrawable = ContextCompat.getDrawable(context, R.drawable.def_circle_selected_background); } if (mIndicatorBackground == null) { mIndicatorBackground = new ColorDrawable(resource.getColor(R.color.indicator_background)); } if (mBannerIndicator == null) { setIndicatorStyle(mIndicatorStyle); } a.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mViewPage.getLayoutParams().height = getLayoutParams().height; super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener) { if (mOnPageChangeListeners == null) { mOnPageChangeListeners = new ArrayList<>(); } mOnPageChangeListeners.add(listener); } public void removeOnPageChangeListener(ViewPager.OnPageChangeListener listener) { if (mOnPageChangeListeners == null) return; mOnPageChangeListeners.remove(listener); } private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) { if (mBannerIndicator != null) { mBannerIndicator.onBannerScrolled(position, offset, offsetPixels); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { ViewPager.OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrolled(position, offset, offsetPixels); } } } } private void dispatchOnPageSelected(int position) { if (mBannerIndicator != null) { mBannerIndicator.onBannerSelected(mBannerList.get(position), position); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { ViewPager.OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageSelected(position); } } } } private void dispatchOnScrollStateChanged(int state) { if (mBannerIndicator != null) { mBannerIndicator.onBannerScrollStateChanged(state); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { ViewPager.OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrollStateChanged(state); } } } } /** * Banneritem. * @param item item index */ public void setCurrentItem(int item) { mViewPage.setCurrentItem(item); } /** * Banneritem * @param item itemindex * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately */ public void setCurrentItem(int item, boolean smoothScroll) { mViewPage.setCurrentItem(item, smoothScroll); } /** * init positionbanner page size,?0 * @param pageInitPosition ?page index */ public void setBannerPageInitPosition(int pageInitPosition) { mBannerPageInitPosition = pageInitPosition; } /** * indicator,LayoutParams * LayoutParams,{@link BannerPagerView#setCustomIndicator(BannerIndicator, LayoutParams)} * @param indicator see {@link BannerIndicator} */ public void setCustomIndicator(BannerIndicator indicator) { setCustomIndicator(indicator, mIndicatorParams); } /** * indicator,?,? * xml? bannerIndicatorLayout * @param indicator see {@link BannerIndicator} * @param params Indicator params */ public void setCustomIndicator(BannerIndicator indicator, LayoutParams params) { if (indicator == null) return; if (mBannerIndicator != indicator) { removeView(mIndicatorView); mBannerIndicator = indicator; mIndicatorView = indicator.getView(); addView(mIndicatorView, params); } } /** * ?? * @param indicatorStyle see {@link IndicatorGravity} */ public void setIndicatorStyle(int indicatorStyle) { mBannerIndicator = IndicatorFactory.createBannerIndicator(getContext(), indicatorStyle); if (mBannerIndicator != null) { mIndicatorView = mBannerIndicator.getView(); addView(mIndicatorView, mIndicatorParams); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mIndicatorView.setBackground(mIndicatorBackground); } else { mIndicatorView.setBackgroundDrawable(mIndicatorBackground); } applyIndicatorAttribute(); } } private void applyIndicatorAttribute() { if (mIndicatorView instanceof IndicatorAttributeSet) { IndicatorAttributeSet indicatorAttributeSet = (IndicatorAttributeSet) mIndicatorView; indicatorAttributeSet.setIndicatorGravity(mIndicatorGravity); indicatorAttributeSet.setPointDrawable(mUnSelectedDrawable, mSelectedDrawable); indicatorAttributeSet.setPointPadding(mPointPadding); } } /** * Set BannerPageTransformer type {@link BannerPagerView#setBannerPageTransformer(boolean, TransformerType)} * * @param transformerType PageTransformer that will modify each page's animation properties * @see TransformerType */ public void setBannerPageTransformer(TransformerType transformerType) { setBannerPageTransformer(true, transformerType); } /** * Set BannerPageTransformer type {@link TransformerType} * * @param reverseDrawingOrder true if the supplied PageTransformer requires page views * to be drawn from last to first instead of first to last. * @param transformerType PageTransformer that will modify each page's animation properties */ public void setBannerPageTransformer(boolean reverseDrawingOrder, TransformerType transformerType) { setBannerPageTransformer(reverseDrawingOrder, PageTransformerFactory.createPageTransformer(transformerType)); } /** * Set a {@link ViewPager.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> * <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 pageTransformer PageTransformer that will modify each page's animation properties */ public void setBannerPageTransformer(boolean reverseDrawingOrder, final ViewPager.PageTransformer pageTransformer) { if (pageTransformer == null) return; mViewPage.setPageTransformer(reverseDrawingOrder, new LoopViewPager.PageTransformer() { @Override public void transformPage(View page, float position) { pageTransformer.transformPage(page, position); } }); } public void setBannerScroller(Scroller scroller) { try { //?? if (mScrollerField == null) { mScrollerField = LoopViewPager.class.getDeclaredField("mScroller"); mScrollerField.setAccessible(true); } mScrollerField.set(mViewPage, scroller); mBannerScroller = scroller; } catch (Exception e) { Log.e(TAG, "Set ViewPager scroller fail", e); } } /** * If the default scroller is used, it will be valid * * @param duration Duration of the scroll in milliseconds. */ public void setBannerScrollerDuration(int duration) { if (mBannerScroller instanceof BannerScroller) { ((BannerScroller) mBannerScroller).setDuration(duration); } } /** * This method only sets the animation interpolator, the internal use of BannerScroller * * @param interpolator {@link Interpolator} * @param duration Duration of the scroll in milliseconds. */ public void setBannerScrollerInterpolator(Interpolator interpolator, int duration) { BannerScroller scroller = new BannerScroller(getContext(), interpolator); if (duration > 0) scroller.setDuration(duration); setBannerScroller(scroller); } public void setBannerPagerAdapter(BannerPageViewAdapter bannerPageAdapter) { if (bannerPageAdapter == null) return; mBannerPageAdapter = bannerPageAdapter; } public void setImageLoader(ImageLoader imageLoader) { this.mImageLoader = imageLoader; } public void setOnBannerClickListener(OnBannerClickListener onBannerClickListener) { this.mOnBannerClickListener = onBannerClickListener; } @Override protected void onFinishInflate() { super.onFinishInflate(); } public void setBanner(List<BannerItem> bannerItems) { if (bannerItems != null && bannerItems.size() > 0) { pauseAutoPlay(); clearAllData(); clearAllView(); mBannerList.addAll(bannerItems); final int originCount = mBannerList.size(); int bannerCount = originCount; mViewPage.setLoopEnable(true); showIndicator(); //,ViewPager,?, //so,?2?,????item?. if (bannerCount < MI_BANNER_COUNT) { //banner ?1,?. //?. if (bannerCount == 1) { hideIndicator(); mViewPage.setLoopEnable(false); } else { bannerCount = 2 * bannerCount; } } mPageAdapter = new BannerPageAdapter(); mPageAdapter.setBannerCount(bannerCount); mViewPage.setAdapter(mPageAdapter); initBannerPosition(originCount); startAutoPlay(); } } private void initBannerPosition(int originCount) { int initPosition = mBannerPageInitPosition > originCount - 1 ? DEFAULT_INIT_POSITION : mBannerPageInitPosition; if (mBannerIndicator != null) { mBannerIndicator.onBannerPageUpdate(originCount); mBannerIndicator.onBannerSelected(mBannerList.get(initPosition), initPosition); } mViewPage.setCurrentItem(initPosition); } public void showIndicator() { if (mIndicatorView == null) return; mIndicatorView.setVisibility(VISIBLE); } public void hideIndicator() { if (mIndicatorView == null) return; mIndicatorView.setVisibility(GONE); } private void clearAllData() { mBannerList.clear(); } private void clearAllView() { mViewPage.removeAllViews(); if (mPageAdapter != null) mPageAdapter.clearViewCache(); } /** * set intervalTime. * * @param intervalTime Play interval time */ public void setIntervalTime(long intervalTime) { this.mIntervalTime = intervalTime; if (isEnableAutoPlay()) { pauseAutoPlay(); startAutoPlay(); } } public void setEnableAutoPlay(boolean enableAutoPlay) { if (enableAutoPlay) { startAutoPlay(); } else { pauseAutoPlay(); } } public boolean isEnableAutoPlay() { return AUTO_PLAY_ENABLE == (mBannerFlags & AUTO_PLAY_MARK); } private boolean isBannerLessThanOne() { return mBannerList.size() <= 1; } private void setBannerFlags(int flags, int mark) { mBannerFlags = (mBannerFlags & ~mark) | (flags & mark); } /** * Pause polling banner play * <p>If your page is not visible, it is recommended that you call this method to reduce unnecessary polling</p> */ public void pauseAutoPlay() { setBannerFlags(AUTO_PLAY_DISABLE, AUTO_PLAY_MARK); mBannerLooperHandler.removeMessages(LOOPER); } /** * If Activity or Fragment is closed, you need to call this method, timely clear memory */ public void destroy() { pauseAutoPlay(); clearAllData(); clearAllView(); } /** * Start polling play banner */ public void startAutoPlay() { if (isEnableAutoPlay()) return; if (isBannerLessThanOne()) return; if (mPageAdapter == null) return; Log.i(TAG, "Start auto play banner"); setBannerFlags(AUTO_PLAY_ENABLE, AUTO_PLAY_MARK); mBannerLooperHandler.sendEmptyMessageDelayed(LOOPER, mIntervalTime); } private void downPauseAutoPlay() { mTouchDown = true; mBannerLooperHandler.removeMessages(LOOPER); Log.i(TAG, "Touch down Pause auto play."); } private void upResumeAutoPlay() { //?? mTouchDown = false; if (isEnableAutoPlay()) { mBannerLooperHandler.sendEmptyMessageDelayed(LOOPER, mIntervalTime); Log.i(TAG, "Touch up resume auto play."); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: upResumeAutoPlay(); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastMotionX = ev.getX(); mLastMotionY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); downPauseAutoPlay(); break; case MotionEvent.ACTION_MOVE: if (isBannerLessThanOne()) break; //?ViewPager. 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 - mLastMotionY); if (BuildConfig.DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } break; default: break; } return super.onInterceptTouchEvent(ev); } private class BannerPageListener implements LoopViewPager.OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { dispatchOnPageScrolled(position, positionOffset, positionOffsetPixels); } @Override public void onPageSelected(int position) { if (checkIndexOutOfBounds(position)) { position = realPosition(position); dispatchOnPageSelected(position); } } @Override public void onPageScrollStateChanged(int state) { dispatchOnScrollStateChanged(state); } } private int realPosition(int position) { return position % mBannerList.size(); } /** * */ private boolean checkIndexOutOfBounds(int position) { return position >= 0 && position <= mPageAdapter.getCount() - 1; } private class BannerPageAdapter extends PagerAdapter { private SparseArray<View> mBannerViewCache = new SparseArray<>(); private int mBannerCount; private void setBannerCount(int bannerCount) { this.mBannerCount = bannerCount; } @Override public int getCount() { return mBannerCount; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Log.i(TAG, "destroyItem item position:" + position); if (checkIndexOutOfBounds(position)) { View bannerView = mBannerViewCache.get(position); if (bannerView == null) return; container.removeView(bannerView); int realPosition = position % mBannerList.size(); mBannerPageAdapter.destroyPageView(bannerView, realPosition); } } @Override public Object instantiateItem(ViewGroup container, int position) { View bannerView = null; Log.i(TAG, "instantiateItem item position:" + position); if (checkIndexOutOfBounds(position)) { int realPosition = realPosition(position); BannerItem bannerItem = mBannerList.get(realPosition); bannerView = mBannerViewCache.get(position); if (bannerView == null) { bannerView = mBannerPageAdapter.createPageView(getContext(), bannerItem, realPosition); bannerView.setTag(bannerItem); bannerView.setOnClickListener(mBannerClickListener); mBannerViewCache.put(position, bannerView); } boolean addSuccess = false; try { container.addView(bannerView); addSuccess = true; } catch (Exception e) { Log.e(TAG, "Banner view already exists parent", e); } if (addSuccess) { mBannerPageAdapter.finishInstantiateItem(bannerView, bannerItem, realPosition); if (mImageLoader != null) mImageLoader.displayBannerImage(getContext(), bannerView, bannerItem); } } return bannerView; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } private void clearViewCache() { mBannerViewCache.clear(); } } }