zoukankan      html  css  js  c++  java
  • 【Android开发日记】之入门篇(十五)——ViewPager+自定义无限ViewPager

    ViewPager

    在 Android 控件中,ViewPager 一直算是使用率比较高的控件,包括首页的banner,tab页的切换都能见到ViewPager的身影。
    viewpager 来源自 v4 支持包 (android.support.v4.view.ViewPager),用于左右切换界面实现tab的效果。其使用方法与 ListView 类似都是搭配一个adapter进行数据适配。

    在布局文件中添加

        <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"  
                android:layout_width="match_parent"  
                android:layout_height="140dp"  />  
    

    通常Viewpager直接往布局文件里面添加就可以了,虽然ViewPager是一个容器类,继承自 ViewGroup,但不建议往里面添加子view。
    之后可以在代码中像调用普通控件一样通过 findViewById(R.id.viewpager) 通过id获取viewpager控件。

    设置 PagerAdapter

    PagerAdapter pagerAdapter = new PagerAdapter() {  
      
        //
        @Override  
        public boolean isViewFromObject(View arg0, Object arg1) {  
    
            return arg0 == arg1;  
        }  
    
        //返回要滑动的VIew的个数
        @Override  
        public int getCount() {  
    
            return viewList.size();  
        }  
    
        //从当前container中删除指定位置(position)的View;
        @Override  
        public void destroyItem(ViewGroup container, int position,  
                Object object) {  
            container.removeView(viewList.get(position));  
    
        }  
    
        @Override  
        public int getItemPosition(Object object) {  
    
            return super.getItemPosition(object);  
        }  
    
        @Override  
        public CharSequence getPageTitle(int position) {  
    
            return titleList.get(position);  
        }  
    
        //做了两件事,第一:将当前视图添加到container中,第二:返回当前View
        @Override  
        public Object instantiateItem(ViewGroup container, int position) {  
            container.addView(viewList.get(position));  
            return viewList.get(position);  
        }  
    
    };
    

    PagerAdapter 支持数据集合的改变,可以调用notifyDataSetChanged方法来进行更新。和BaseAdapter非常相似。

    设置 ViewPager

    viewPager = (ViewPager) findViewById(R.id.viewpager);  
    LayoutInflater inflater=getLayoutInflater();  
    view1 = inflater.inflate(R.layout.layout1, null);  
    view2 = inflater.inflate(R.layout.layout2,null);  
    view3 = inflater.inflate(R.layout.layout3, null);  
      
    viewList = new ArrayList<View>();// 将要分页显示的View装入数组中  
    viewList.add(view1);  
    viewList.add(view2);  
    viewList.add(view3); 
    
    //初始化 pagerAdapter 
    PagerAdapter pagerAdapter = new PagerAdapter() {...}
    viewPager.setAdapter(pagerAdapter);
    
    

    此时PagerAdapter包含了viewList数据,此时就能显示到Viewpager中。

    ViewPager扩展

    PagerTitleStrip 和 PagerTabStrip

    这两个类都属于v4 support包中的类,两个都是是ViewPager的一个关于当前页面、上一个页面和下一个页面的指示器。
    其作为ViewPager控件的一个子控件被被添加在XML布局文件中,每个页面的标题是通过适配器的getPageTitle(int)函数提供给ViewPager的。
    但两者又有不同:
    1、 PagerTabStrip 是可交互的,点哪滚到哪;
    2、 PagerTabStrip 在当前页面下,会有一个下划线条来提示当前页面的Tab是哪个。
    这里以 PagerTabStrip 为例

    使用

    添加布局

    <android.support.v4.view.ViewPager  
        android:id="@+id/viewpager"  
        android:layout_width="match_parent"  
        android:layout_height="140dp"  
        android:layout_gravity="center">  
          
            <android.support.v4.view.PagerTabStrip  
            android:id="@+id/pagertab"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"   
            android:layout_gravity="top"/>  
              
    </android.support.v4.view.ViewPager>
    

    添加代码

        titleList = new ArrayList<String>();// 每个页面的Title数据  
        titleList.add("1111");  
        titleList.add("2222");  
        titleList.add("3333");
    

    通过PagerAdaper的getPageTitle(int position)来设置标题

    @Override  
    public CharSequence getPageTitle(int position) {  
        return titleList.get(position);  
    }
    

    缺陷

    但由于 PagerTabStrip 的局限性,大家一般都会自定义一个指示器,通过ViewPager.OnPageChangeListener绑定。
    同时安利一下 google design 包里面的 TabLayout 和 最新推出的Bottom navigation

    开源项目:无限 viewpager——一款可以高度自定义的 slider

    这是一款我刚写的开源库,起初写这个是因为我想要一个可以高度自定义化的轮播器,它包括了一下几个特点:

    使用自己项目中的ImageLoader,而不是被迫使用库中的图片加载器;
    库中内置几款常用的指示器,也可以创建自己想要的指示器;
    丰富多彩的转场动画,亦可以发挥你的创意创造新的特效;
    可以为每一页建立动画效果。

    Github地址

    关于无限轮播

    一般的ViewPager播放到最后一个位置时,若要返回到第一个位置则必须从右向左,作为Banner时这是不自然的过渡效果。
    我们知道ViewPager显示的个数是由PagerAdaper的getCount()方法决定的,既然如此那我们可以给它赋予一个极大的值使ViewPager不断向右轮播,这也是整个无限轮播的核心思想。

    public class InfinitePagerAdapter extends PagerAdapter {
    
        private static final String TAG = "InfinitePagerAdapter";
        private static final boolean DEBUG = false;
    
        private BaseSliderAdapter adapter;
    
        public InfinitePagerAdapter(BaseSliderAdapter adapter) {
            this.adapter = adapter;
            adapter.registerDataSetObserver(new DataSetObserver() {
                @Override
                public void onChanged() {
                    notifyDataSetChanged();
                    super.onChanged();
                }
            });
        }
    
        public BaseSliderAdapter getRealAdapter() {
            return this.adapter;
        }
    
        @Override
        public CharSequence getPageTitle(int position) {
            return adapter.getPageTitle(position % getRealCount());
        }
    
        @Override
        public int getCount() {
            // warning: scrolling to very high values (1,000,000+) results in
            // strange drawing behaviour
            if (getRealCount() == 0) return 0;
            if (getRealCount() == 1) return 1;
            return Integer.MAX_VALUE;
        }
    
        /**
         * @return the {@link #getCount()} result of the wrapped adapter
         */
        public int getRealCount() {
            return adapter.getCount();
        }
    
        public BaseSliderView getSliderView(int position) {
            return adapter.getSliderView(position % getRealCount());
        }
    
        @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);
            }
        }
    }
    

    可以看到在代码中还存在着 BaseSliderAdapter 另一个 PagerAdapter ,这是一个正常的PagerAdapter,它既是数据的真正载体同时也起着维护viewpager真正个数的作用。

    但用这个方法实现的无限轮播存在着一个很大的缺陷,在 page 页少于3个的情况下会出现问题,这是由于ViewPager本身的机制导致的。
    简单来说,ViewPager在显示的时候会同时存在3个page页,当前显示页,当前显示的前一页,当前显示的后一页。当我们轮播到下一页时,原来的前一页会被回收,原来的当前页变成前一页,原来的后一页变成当前页,同时会加载出新的一页作为后一页。
    而在无限轮播小于等于3页时,由于前一页可能没有被回收就被当做新的一页加载到后一页中,这就导致了view重复被ViewGroup添加,而在Android中View只能被允许拥有一个ParentView,这里就出现了问题。
    所以在page页少于等于3页的时候还是要使用基础的 PagerAdapter。

    关于指示器

    指示器可以通过继承 ViewPager.OnPageChangeListener与ViewPager保持联动,其监听器包括以下方法:

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // 滑动时的参数变化
    }
    
    @Override
    public void onPageSelected(int position) {
        // 滑动到第几页
    }
    
    @Override
    public void onPageScrollStateChanged(int state) {
       // 滑动时的状态变化
    }
    

    剩下的就是看你怎么定义指示器的界面,附上指示器演示图和一个自定义的指示器代码
    indicator

    public class CirclePageIndicator extends View implements PageIndicator {
        private static final int INVALID_POINTER = -1;
    
        private float mRadius;
        private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
        private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
        private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
        private ViewPager mViewPager;
        private ViewPager.OnPageChangeListener mListener;
        private int mCurrentPage;
        private int mSnapPage;
        private float mPageOffset;
        private int mScrollState;
        private int mOrientation;
        private boolean mCentered;
        private boolean mSnap;
    
        private int mTouchSlop;
        private float mLastMotionX = -1;
        private int mActivePointerId = INVALID_POINTER;
        private boolean mIsDragging;
    
    
        public CirclePageIndicator(Context context) {
            this(context, null);
        }
    
        public CirclePageIndicator(Context context, AttributeSet attrs) {
            this(context, attrs, R.attr.vpiCirclePageIndicatorStyle);
        }
    
        public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            if (isInEditMode()) return;
    
            //Load defaults from resources
            final Resources res = getResources();
            final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
            final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
            final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
            final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
            final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
            final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
            final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
            final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);
    
            //Retrieve styles attributes
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
    
            mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
            mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
            mPaintPageFill.setStyle(Style.FILL);
            mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
            mPaintStroke.setStyle(Style.STROKE);
            mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
            mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
            mPaintFill.setStyle(Style.FILL);
            mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor));
            mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius);
            mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);
    
            Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
            if (background != null) {
                setBackgroundDrawable(background);
            }
    
            a.recycle();
    
            final ViewConfiguration configuration = ViewConfiguration.get(context);
            mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
        }
    
    
        public void setCentered(boolean centered) {
            mCentered = centered;
            invalidate();
        }
    
        public boolean isCentered() {
            return mCentered;
        }
    
        public void setPageColor(int pageColor) {
            mPaintPageFill.setColor(pageColor);
            invalidate();
        }
    
        public int getPageColor() {
            return mPaintPageFill.getColor();
        }
    
        public void setFillColor(int fillColor) {
            mPaintFill.setColor(fillColor);
            invalidate();
        }
    
        public int getFillColor() {
            return mPaintFill.getColor();
        }
    
        public void setOrientation(int orientation) {
            switch (orientation) {
                case HORIZONTAL:
                case VERTICAL:
                    mOrientation = orientation;
                    requestLayout();
                    break;
    
                default:
                    throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
            }
        }
    
        public int getOrientation() {
            return mOrientation;
        }
    
        public void setStrokeColor(int strokeColor) {
            mPaintStroke.setColor(strokeColor);
            invalidate();
        }
    
        public int getStrokeColor() {
            return mPaintStroke.getColor();
        }
    
        public void setStrokeWidth(float strokeWidth) {
            mPaintStroke.setStrokeWidth(strokeWidth);
            invalidate();
        }
    
        public float getStrokeWidth() {
            return mPaintStroke.getStrokeWidth();
        }
    
        public void setRadius(float radius) {
            mRadius = radius;
            invalidate();
        }
    
        public float getRadius() {
            return mRadius;
        }
    
        public void setSnap(boolean snap) {
            mSnap = snap;
            invalidate();
        }
    
        public boolean isSnap() {
            return mSnap;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            if (mViewPager == null) {
                return;
            }
            int count = mViewPager.getAdapter().getCount();
            if (mViewPager.getAdapter() instanceof InfinitePagerAdapter) {
                count = ((InfinitePagerAdapter) mViewPager.getAdapter()).getRealCount();
                mCurrentPage = mCurrentPage % count;
            }
            if (count == 0) {
                return;
            }
    
            if (mCurrentPage >= count) {
                setCurrentItem(count - 1);
                return;
            }
    
            int longSize;
            int longPaddingBefore;
            int longPaddingAfter;
            int shortPaddingBefore;
            if (mOrientation == HORIZONTAL) {
                longSize = getWidth();
                longPaddingBefore = getPaddingLeft();
                longPaddingAfter = getPaddingRight();
                shortPaddingBefore = getPaddingTop();
            } else {
                longSize = getHeight();
                longPaddingBefore = getPaddingTop();
                longPaddingAfter = getPaddingBottom();
                shortPaddingBefore = getPaddingLeft();
            }
    
            final float threeRadius = mRadius * 3;
            final float shortOffset = shortPaddingBefore + mRadius;
            float longOffset = longPaddingBefore + mRadius;
            if (mCentered) {
                longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius-mRadius) / 2.0f);
            }
    
            float dX;
            float dY;
    
            float pageFillRadius = mRadius;
            if (mPaintStroke.getStrokeWidth() > 0) {
                pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
            }
    
            //Draw stroked circles
            for (int iLoop = 0; iLoop < count; iLoop++) {
                float drawLong = longOffset + (iLoop * threeRadius);
                if (mOrientation == HORIZONTAL) {
                    dX = drawLong;
                    dY = shortOffset;
                } else {
                    dX = shortOffset;
                    dY = drawLong;
                }
                // Only paint fill if not completely transparent
                if (mPaintPageFill.getAlpha() > 0) {
                    canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
                }
    
                // Only paint stroke if a stroke width was non-zero
                if (pageFillRadius != mRadius) {
                    canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
                }
            }
    
            //Draw the filled circle according to the current scroll
            float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
            if (!mSnap && mCurrentPage < count - 1) {
                cx += mPageOffset * threeRadius;
            }
            if (mOrientation == HORIZONTAL) {
                dX = longOffset + cx;
                dY = shortOffset;
            } else {
                dX = shortOffset;
                dY = longOffset + cx;
            }
            canvas.drawCircle(dX, dY, mRadius, mPaintFill);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (super.onTouchEvent(ev)) {
                return true;
            }
            if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
                return false;
            }
    
            final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                    mLastMotionX = ev.getX();
                    break;
                case MotionEvent.ACTION_MOVE: {
                    final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
                    final float deltaX = x - mLastMotionX;
    
                    if (!mIsDragging) {
                        if (Math.abs(deltaX) > mTouchSlop) {
                            mIsDragging = true;
                        }
                    }
    
                    if (mIsDragging) {
                        mLastMotionX = x;
                        if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
                            mViewPager.fakeDragBy(deltaX);
                        }
                    }
                    break;
                }
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    if (!mIsDragging) {
                        int count = mViewPager.getAdapter().getCount();
                        if (mViewPager.getAdapter() instanceof InfinitePagerAdapter) {
                            count = ((InfinitePagerAdapter) mViewPager.getAdapter()).getRealCount();
                        }
                        final int width = getWidth();
                        final float halfWidth = width / 2f;
                        final float sixthWidth = width / 6f;
    
                        if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
                            if (action != MotionEvent.ACTION_CANCEL) {
                                mViewPager.setCurrentItem(mCurrentPage - 1);
                            }
                            return true;
                        } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
                            if (action != MotionEvent.ACTION_CANCEL) {
                                mViewPager.setCurrentItem(mCurrentPage + 1);
                            }
                            return true;
                        }
                    }
                    mIsDragging = false;
                    mActivePointerId = INVALID_POINTER;
                    if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
                    break;
                case MotionEventCompat.ACTION_POINTER_DOWN: {
                    final int index = MotionEventCompat.getActionIndex(ev);
                    mLastMotionX = MotionEventCompat.getX(ev, index);
                    mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                    break;
                }
                case MotionEventCompat.ACTION_POINTER_UP:
                    final int pointerIndex = MotionEventCompat.getActionIndex(ev);
                    final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                    if (pointerId == mActivePointerId) {
                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                        mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
                    }
                    mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
                    break;
            }
            return true;
        }
    
        @Override
        public void setViewPager(ViewPager view) {
            if (mViewPager == view) {
                return;
            }
            if (view.getAdapter() == null) {
                throw new IllegalStateException("ViewPager does not have adapter instance.");
            }
            mViewPager = view;
            mViewPager.addOnPageChangeListener(this);
            invalidate();
        }
    
        @Override
        public void setViewPager(ViewPager view, int initialPosition) {
            setViewPager(view);
            setCurrentItem(initialPosition);
        }
    
        @Override
        public void setCurrentItem(int item) {
            if (mViewPager == null) {
                throw new IllegalStateException("ViewPager has not been bound.");
            }
            mViewPager.setCurrentItem(item);
            mCurrentPage = item;
            invalidate();
        }
    
        @Override
        public void notifyDataSetChanged() {
            invalidate();
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
            mScrollState = state;
    
            if (mListener != null) {
                mListener.onPageScrollStateChanged(state);
            }
        }
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if (mViewPager.getAdapter() instanceof InfinitePagerAdapter) {
                position = position % ((InfinitePagerAdapter) mViewPager.getAdapter()).getRealCount();
            }
            mCurrentPage = position;
            mPageOffset = positionOffset;
            invalidate();
    
            if (mListener != null) {
                mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
            }
        }
    
        @Override
        public void onPageSelected(int position) {
            if (mViewPager.getAdapter() instanceof InfinitePagerAdapter) {
                position = position % ((InfinitePagerAdapter) mViewPager.getAdapter()).getRealCount();
            }
            if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
                mCurrentPage = position;
                mSnapPage = position;
                invalidate();
            }
    
            if (mListener != null) {
                mListener.onPageSelected(position);
            }
        }
    
        @Override
        public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
            mListener = listener;
        }
    
        /*
         * (non-Javadoc)
         *
         * @see android.view.View#onMeasure(int, int)
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == HORIZONTAL) {
                setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
            } else {
                setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
            }
        }
    
        /**
         * Determines the width of this view
         *
         * @param measureSpec A measureSpec packed into an int
         * @return The width of the view, honoring constraints from measureSpec
         */
        private int measureLong(int measureSpec) {
            int result;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
                //We were told how big to be
                result = specSize;
            } else {
                //Calculate the width according the views count
                int count = mViewPager.getAdapter().getCount();
                if (mViewPager.getAdapter() instanceof InfinitePagerAdapter) {
                    count = ((InfinitePagerAdapter) mViewPager.getAdapter()).getRealCount();
                }
                result = (int) (getPaddingLeft() + getPaddingRight()
                        + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
                //Respect AT_MOST value if that was what is called for by measureSpec
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }
    
        /**
         * Determines the height of this view
         *
         * @param measureSpec A measureSpec packed into an int
         * @return The height of the view, honoring constraints from measureSpec
         */
        private int measureShort(int measureSpec) {
            int result;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            if (specMode == MeasureSpec.EXACTLY) {
                //We were told how big to be
                result = specSize;
            } else {
                //Measure the height
                result = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
                //Respect AT_MOST value if that was what is called for by measureSpec
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }
    
        @Override
        public void onRestoreInstanceState(Parcelable state) {
            SavedState savedState = (SavedState) state;
            super.onRestoreInstanceState(savedState.getSuperState());
            mCurrentPage = savedState.currentPage;
            mSnapPage = savedState.currentPage;
            requestLayout();
        }
    
        @Override
        public Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();
            SavedState savedState = new SavedState(superState);
            savedState.currentPage = mCurrentPage;
            return savedState;
        }
    
        static class SavedState extends BaseSavedState {
            int currentPage;
    
            public SavedState(Parcelable superState) {
                super(superState);
            }
    
            private SavedState(Parcel in) {
                super(in);
                currentPage = in.readInt();
            }
    
            @Override
            public void writeToParcel(Parcel dest, int flags) {
                super.writeToParcel(dest, flags);
                dest.writeInt(currentPage);
            }
    
            @SuppressWarnings("UnusedDeclaration")
            public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
                @Override
                public SavedState createFromParcel(Parcel in) {
                    return new SavedState(in);
                }
    
                @Override
                public SavedState[] newArray(int size) {
                    return new SavedState[size];
                }
            };
        }
    }
    

    关于转场动画

    ViewPager有个方法叫做setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) 用于设置ViewPager切换时的动画效果
    但注意这只能在3.0及其以后使用。因为View的动画使用的是属性动画,而属性动画是3.0才推出。当然这个问题可以克服,首先先使用nineoldandroids让动画在3.0之前也能跑起来,然后再去修改ViewPager的源码

        public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {  
           if (Build.VERSION.SDK_INT >= 11) {  
               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();  
           }  
       } 
    

    去除if (Build.VERSION.SDK_INT >= 11)这个if判断就行了。
    所有的PageTransformer都要继承ViewPager.PageTransformer接口,其中包含的方法只有一个

        @Override
        public void transformPage(View view, float position) {
    
        }
    

    其中position反映的是view的位置变化。
    假设现在ViewPager在A页现在滑出B页,则:
    A页的position变化就是( 0, -1]
    B页的position变化就是[ 1 , 0 ]
    根据这个position就可以做出多样的转场变化了。
    Transform

    关于pager动画

    同样要利用 ViewPager.OnPageChangeListener 监听ViewPager的变化以实现每个界面的变化。
    Animation接口

    public interface OnAnimationListener {
        void onNextAnimationStart(BaseSliderView slider);
    
        void onNextAnimationEnd(BaseSliderView slider);
    
        void onPreAnimationStart(BaseSliderView slider);
    
        void onPreAnimationEnd(BaseSliderView slider);
    }
    

    监听变化

    /**
     * A {@link ViewPager} that allows define custom animation
     */
    public class AnimationViewPager extends ViewPager {
    
        private int position = 0, prePositon = 0;
        private OnAnimationListener animationListener;
        private BaseSliderView slider, preSlider;
        private boolean animating = false;
    
        public AnimationViewPager(Context context) {
            super(context);
            initViewPager();
        }
    
        public AnimationViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
            initViewPager();
        }
    
        private void initViewPager() {
            addOnPageChangeListener(new OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    if (position == 0 && positionOffset == 0) {
                        animateSliderEnd(position);
                        if (slider != null)
                            animationListener.onNextAnimationEnd(slider);
                    }
                }
    
                @Override
                public void onPageSelected(int position) {
                    prePositon = AnimationViewPager.this.position;
                    AnimationViewPager.this.position = position;
                    if (prePositon > position)
                        animateSliderStart(position, position + 1);
                    if (prePositon < position)
                        animateSliderStart(position, position - 1);
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    if (state == SCROLL_STATE_IDLE) {
                        animateSliderEnd(position);
                    }
                }
            });
        }
    
        private void animateSliderStart(int position, int prePositon) {
            if (animationListener == null) return;
            BaseSliderView slider = null, preSlider = null;
            if (getAdapter() instanceof InfinitePagerAdapter) {
                InfinitePagerAdapter pagerAdapter = (InfinitePagerAdapter) getAdapter();
                slider = pagerAdapter.getSliderView(position);
                preSlider = pagerAdapter.getSliderView(prePositon);
            } else if (getAdapter() instanceof BaseSliderAdapter) {
                BaseSliderAdapter sliderAdapter = (BaseSliderAdapter) getAdapter();
                slider = sliderAdapter.getSliderView(position);
                preSlider = sliderAdapter.getSliderView(prePositon);
            }
            if (slider != null)
                animationListener.onNextAnimationStart(slider);
            if (preSlider != null)
                animationListener.onPreAnimationStart(preSlider);
    
        }
    
        private void animateSliderEnd(int position) {
            if (animationListener == null) return;
            if (getAdapter() instanceof InfinitePagerAdapter) {
                InfinitePagerAdapter pagerAdapter = (InfinitePagerAdapter) getAdapter();
                if (slider == null) slider = pagerAdapter.getSliderView(position);
                else if (slider != pagerAdapter.getSliderView(position)) {
                    preSlider = slider;
                    slider = pagerAdapter.getSliderView(position);
                }
            } else if (getAdapter() instanceof BaseSliderAdapter) {
                BaseSliderAdapter sliderAdapter = (BaseSliderAdapter) getAdapter();
                if (slider == null) slider = sliderAdapter.getSliderView(position);
                else if (slider != sliderAdapter.getSliderView(position)) {
                    preSlider = slider;
                    slider = sliderAdapter.getSliderView(position);
                }
            }
            if (slider != null)
                animationListener.onNextAnimationEnd(slider);
            if (preSlider != null)
                animationListener.onPreAnimationEnd(preSlider);
            animating = false;
        }
    
        public OnAnimationListener getAnimationListener() {
            return animationListener;
        }
    
        public void setAnimationListener(OnAnimationListener animationListener) {
            this.animationListener = animationListener;
        }
    }
    

    继承OnAnimationListener接口实现后的例子

    public class DefaultDescriptionAnimation implements OnAnimationListener {
    
        private final long DURATION = 300;
    
        @Override
        public void onNextAnimationStart(BaseSliderView slider) {
            Log.d("simpleSlider", "onNextAnimationStart:" + slider.getPageTitle());
        }
    
        @Override
        public void onNextAnimationEnd(BaseSliderView slider) {
            Log.d("simpleSlider", "onNextAnimationEnd:" + slider.getPageTitle());
            DescriptionSliderView sliderView = (DescriptionSliderView) slider;
            if (sliderView.getTitleLayout().getVisibility() != View.VISIBLE)
                translateShowAnimate(sliderView.getTitleLayout());
        }
    
        @Override
        public void onPreAnimationStart(BaseSliderView slider) {
            Log.d("simpleSlider", "onPreAnimationStart:" + slider.getPageTitle());
        }
    
        @Override
        public void onPreAnimationEnd(BaseSliderView slider) {
            Log.d("simpleSlider", "onPreAnimationEnd:" + slider.getPageTitle());
            DescriptionSliderView sliderView = (DescriptionSliderView) slider;
            alphaHideAnimate(sliderView.getTitleLayout());
        }
    
        public void alphaHideAnimate(View v) {
            v.clearAnimation();
            v.setVisibility(View.INVISIBLE);
            AlphaAnimation aa = new AlphaAnimation(1, 0);
            aa.setDuration(DURATION);
            v.startAnimation(aa);
        }
    
        public void translateShowAnimate(View v) {
            v.setVisibility(View.VISIBLE);
            v.clearAnimation();
            TranslateAnimation ta = new TranslateAnimation(0, 0, v.getHeight(), 0);
            ta.setDuration(DURATION);
            v.startAnimation(ta);
        }
    }
    

    Transform

    结束语

    今天的ViewPager的介绍就到这了,若大家有问题或者想要在Android上交流可以大胆的联系我!


    Github,欢迎follow或star
    blog,个人小窝

  • 相关阅读:
    包管理器Bower使用手冊之中的一个
    开机就提示“请安装TCP/IP协议,error=10106”的解决的方法
    Apache Thrift的简单使用
    Python机器学习实战&lt;一&gt;:环境的配置
    数据库期末考试复习题 第二�
    数据集成之主数据管理(一)基础概念篇
    Cocos2d-x开发中C++内存管理
    html中#include file的使用方法
    网页的背景图片代码
    TEST
  • 原文地址:https://www.cnblogs.com/cpacm/p/5555221.html
Copyright © 2011-2022 走看看