阿里开源UItraViewPage源码分析

UltraViewPager是一个功能丰富的ViewPager实现,支持多种滑动效果和指示器。它提供了横向和纵向滑动、循环滚动、定时滚动等功能,并内置了指示器和切换动效。使用时,可以通过设置最大宽高等属性来定制布局。源码分析中,着重讲解了IUltraIndicatorBuilder接口和UltraViewPager的实现,包括指示器颜色、边距等设置,以及滚动模式、无限循环等功能的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

UltraViewPager是一个封装多种特性的ViewPager,主要是为多页面切换场景提供统一解决方案。

主要功能

  • 支持横向滑动/纵向滑动
  • 支持一屏内显示多页
  • 支持循环滚动
  • 支持定时滚动,计时器使用Handler实现
  • 支持设置ViewPager的最大宽高
  • setRatio按比例显示UltraviewPager
  • 内置indicator,只需简单设置几个属性就可以完成展示,支持圆点和Icon
  • 内置两种页面切换动效

使用方法

版本请参考mvn repository上的最新版本(目前最新版本是1.0.4),最新的 aar 都会发布到 jcenter 和 MavenCentral 上,确保配置了这两个仓库源,然后引入aar依赖:

//gradle
compile ('com.alibaba.android:ultraviewpager:1.0.4@aar') {
    transitive = true
}

或者maven

//pom.xml in maven
<dependency>
  <groupId>com.alibaba.android</groupId>
  <artifactId>ultraviewpager</artifactId>
  <version>1.0.4</version>
  <type>aar</type>
</dependency>

在layout中使用UltraViewPager: activity_pager.xml

<com.tmall.ultraviewpager.UltraViewPager
    android:id="@+id/ultra_viewpager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="@android:color/darker_gray" />

可以参考以下步骤使用UltraViewPager:

//绑定控件
UltraViewPager ultraViewPager = (UltraViewPager)findViewById(R.id.ultra_viewpager);
//设置滑动的方向
ultraViewPager.setScrollMode(UltraViewPager.ScrollMode.HORIZONTAL);
//UltraPagerAdapter 绑定子view到UltraViewPager
PagerAdapter adapter = new UltraPagerAdapter(false);
ultraViewPager.setAdapter(adapter);

//内置indicator初始化
ultraViewPager.initIndicator();
//设置indicator样式
ultraViewPager.getIndicator()
    .setOrientation(UltraViewPager.Orientation.HORIZONTAL)
    .setFocusColor(Color.GREEN)  //设置选择时为绿色
    .setNormalColor(Color.WHITE) //设置未选择时为白色
 .setRadius((int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));//设置大小
//设置indicator对齐方式
ultraViewPager.getIndicator().setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
//构造indicator,绑定到UltraViewPager
ultraViewPager.getIndicator().build();

//设定页面循环播放
ultraViewPager.setInfiniteLoop(true);
//设定页面自动切换  间隔2秒
ultraViewPager.setAutoScroll(2000);
//取消页面自动切换
ultraViewPager.disableAutoScroll();

其他方法

//设定多银幕的显示比例 1f全占满整个横屏  0.6f合适
ultraViewPager.setMultiScreen(0.9f);
//设定比例 childHeigh =childWidth / itemRatio
//例: 1f时宽度=高度  0.8f时,高度略大于宽度
 ultraViewPager.setItemRatio(0.8f); 
 //设定最大宽度和高度
 ultraViewPager.setMaxHeight(100);
 ultraViewPager.setMaxWidth(80);
 //设置三种模式的滑动变压效果
ultraViewPager.setPageTransformer(false, new UltraScaleTransformer());
ultraViewPager.setPageTransformer(false, new UltraDepthScaleTransformer());

UltraVerticalTransformer //垂直不常用的

//设置不使用内置Indicator
 ultraViewPager.disableIndicator();

源码分析

整个UItraViewPage的类如下

这里写图片描述

阿里出品的开源框架和开源的类中,个人比较看好的则是阿里的代码规范,见文知意,所以阅读他的源码很容易理解。

其中transformer包中是几个滑动显示的效果,内容比较简单,查看相关类就可以。

IUltraIndicatorBuilder接口

下面分析他的IUltraIndicatorBuilder接口
在这个接口中给出了如下的方法:

  • 设置选中/未选中状态下的指示器颜色
  • 设置边距、圆角半径、方向和位置
  • 提供一个构造方法
public interface IUltraIndicatorBuilder {
    /**
     * Set focused color for indicator.
     * 设置指示器的颜色
     * @param focusColor
     * @return
     */
    IUltraIndicatorBuilder setFocusColor(int focusColor);

    /**
     * Set normal color for indicator.
     * 设置未选择时指示器的颜色
     * @param normalColor
     * @return
     */
    IUltraIndicatorBuilder setNormalColor(int normalColor);

    /**
     * Set stroke color for indicator.
     * 设置指示器表面颜色
     * @param strokeColor
     * @return
     */
    IUltraIndicatorBuilder setStrokeColor(int strokeColor);

    /**
     * Set stroke width for indicator.
     * 设置指示器的宽度
     * @param strokeWidth
     * @return
     */
    IUltraIndicatorBuilder setStrokeWidth(int strokeWidth);

    /**
     * Set spacing between indicator item ,the default value is item's height.
     * 设置间距指标项,默认值是项的高度。
     * @param indicatorPadding
     * @return
     */
    IUltraIndicatorBuilder setIndicatorPadding(int indicatorPadding);

    /**
     * Set the corner radius of the indicator item.
     * 设置指示器的圆角半径。
     * @param radius
     * @return
     */
    IUltraIndicatorBuilder setRadius(int radius);

    /**
     * Sets the orientation of the layout.
     * 设置布局的方向
     * @param orientation
     * @return
     */
    IUltraIndicatorBuilder setOrientation(UltraViewPager.Orientation orientation);

    /**
     * Set the location at which the indicator should appear on the screen.
     * 设置指示器应该出现在屏幕上的位置。
     * @param gravity android.view.Gravity
     * @return
     */
    IUltraIndicatorBuilder setGravity(int gravity);

    /**
     * Set focused resource ID for indicator.
     * 为指示器设置选中状态下的资源ID。
     * @param focusResId
     * @return
     */
    IUltraIndicatorBuilder setFocusResId(int focusResId);

    /**
     * Set normal resource ID for indicator.
     * 为指示器设置未选中状态下的资源ID。
     * @param normalResId
     * @return
     */
    IUltraIndicatorBuilder setNormalResId(int normalResId);

    /**
     * Set focused icon for indicator.
     * 为指示器设置聚焦图标。
     * @param bitmap
     * @return
     */
    IUltraIndicatorBuilder setFocusIcon(Bitmap bitmap);

    /**
     * Set normal icon for indicator.
     * 为指示器设置未选中的图标。
     * @param bitmap
     * @return
     */
    IUltraIndicatorBuilder setNormalIcon(Bitmap bitmap);

    /**
     * Set margins for indicator.
     * 为指示器设置边距
     * @param left   the left margin in pixels
     * @param top    the top margin in pixels
     * @param right  the right margin in pixels
     * @param bottom the bottom margin in pixels
     * @return
     */
    IUltraIndicatorBuilder setMargin(int left, int top, int right, int bottom);

    /**
     * Combine all of the options that have been set and return a new IUltraIndicatorBuilder object.
     * 将所有已设置的选项组合在一起并返回 IUltraIndicatorBuilder
     */
    void build();
}

IUltraViewPagerFeature接口

其中包含以下功能:

  • 5种初始化的方法
  • 移除指示器
  • 启动/禁用自动滑动模式
  • 设置无限循环模式
  • 为viewpage提供最大的宽度/高度
  • 设置纵横比
  • 设置滚动模式
  • 禁用滚动方向
  • 滚动到下一页,当到达最后一个页面时返回第一个页面
  • 设置多屏幕模式
  • 将子view高度自动调整到ViewPager的高度
  • 根据纵横比调整子view的高度
  • 使用像素设置两个页面之间的空隙
  • 设置item边距包括上下左右和左右
interface IUltraViewPagerFeature {
    /**
     * Constructs a indicator with no options. this indicator support set-Method in chained mode.
     * 构造一个没有选项的指示器。这个指示器支持固定模式下的方法。
     * meanwhile focusColor and normalColor are necessary,or the indicator won't be show.
     * 与此同时,焦点颜色和正常颜色是必要的,否则指示器就不会显示出来。
     *
     */
    IUltraIndicatorBuilder initIndicator();

    /**
     * Set options for indicator
     * 指示器的设置选项
     *
     * @param focusColor    defines the color when indicator is focused.
     * @param normalColor   defines the color when indicator is in the default state (not focused).
     * @param radiusInPixel defines the radius of indicator item.
     * @param gravity       specifies how to align the indicator. for example, using Gravity.BOTTOM | Gravity.RIGHT
     */
    IUltraIndicatorBuilder initIndicator(int focusColor, int normalColor, int radiusInPixel, int gravity);

    /**
     * Set options for indicator
     *
     * @param focusColor    defines the color when indicator is focused.
     * @param normalColor   defines the color when indicator is in the default state (not focused).
     * @param strokeColor   stroke color
     * @param strokeWidth   stroke width
     * @param radiusInPixel the radius of indicator item.
     * @param gravity       specifies how to align the indicator. for example, using Gravity.BOTTOM | Gravity.RIGHT
     */
    IUltraIndicatorBuilder initIndicator(int focusColor, int normalColor, int strokeColor, int strokeWidth, int radiusInPixel, int gravity);

    /**
     * Set options for indicator
     *
     * @param focusResId  defines the resource id when indicator is focused.
     * @param normalResId defines the resource id  when indicator is in the default state (not focused).
     * @param gravity     specifies how to align the indicator. for example, using Gravity.BOTTOM | Gravity.RIGHT
     */
    IUltraIndicatorBuilder initIndicator(int focusResId, int normalResId, int gravity);

    /**
     * @param focusBitmap  defines the bitmap when indicator is focused
     * @param normalBitmap defines the bitmap when indicator is in the default state (not focused).
     * @param gravity      specifies how to align the indicator. for example, using Gravity.BOTTOM | Gravity.RIGHT
     * @return
     */
    IUltraIndicatorBuilder initIndicator(Bitmap focusBitmap, Bitmap normalBitmap, int gravity);

    /**
     * Remove indicator
     * 移除指示器
     */
    void disableIndicator();

    /**
     * Enable auto-scroll mode
     * 启动自动滑动模式
     *
     * @param intervalInMillis The interval time to scroll in milliseconds.
     *                         以毫秒为轴滚动的间隔时间。
     */
    void setAutoScroll(int intervalInMillis);

    /**
     * Disable auto-scroll mode
     * 禁用自动滑动模式
     *
     */
    void disableAutoScroll();

    /**
     * Set an infinite loop
     * 设置一个无限循环
     *
     * @param enable enable or disable
     */
    void setInfiniteLoop(boolean enable);

    /**
     * Supply a maximum width for this ViewPager.
     * 为viewpager提供最大宽度。
     *
     * @param width width
     */
    void setMaxWidth(int width);

    /**
     * Supply a maximum height for this ViewPager.
     * 为viewpager提供最大高度。
     *
     * @param height height
     */
    void setMaxHeight(int height);

    /**
     * Set the aspect ratio for UltraViewPager.
     * 设置纵横比。
     *
     * @param ratio
     */
    void setRatio(float ratio);

    /**
     * Set scroll mode for UltraViewPager.
     * 设置滚动模式
     *
     * @param scrollMode UltraViewPager.ScrollMode.HORIZONTAL or UltraViewPager.ScrollMode.VERTICAL
     */
    void setScrollMode(UltraViewPager.ScrollMode scrollMode);

    /**
     * Disable scroll direction. the default value is ScrollDirection.NONE
     * 禁用滚动方向 默认是ScrollDirection.NONE
     *
     * @param direction NONE, BACKWARD, FORWARD
     */
    void disableScrollDirection(UltraViewPager.ScrollDirection direction);

    /**
     * Scroll to the next page, and return to the first page when the last page is reached.
     * 滚动到下一页,当到达最后一个页面时返回到第一个页面。
     */
    void scrollNextPage();

    /**
     * Set multi-screen mode , the aspect ratio of PageViewer should less than or equal to 1.0f
     * 设置多屏幕模式,页面查看器的纵横比应该小于或等于1.0 f。
     *
     */
    void setMultiScreen(float ratio);

    /**
     * Adjust the height of the ViewPager to the height of child automatically.
     * 将子view高度自动调整到ViewPager的高度
     */
    void setAutoMeasureHeight(boolean status);

    /**
     * Adjust the height of child item view with aspect ratio.
     * 用纵横比调整子view的高度。
     *
     * @param ratio aspect ratio
     */
    void setItemRatio(double ratio);

    /**
     * Set the gap between two pages in pixel
     * 使用像素设置两个页面之间的空隙
     *
     * @param pixel
     */
    void setHGap(int pixel);

    /**
     * Set item margin
     *
     * @param left   the left margin in pixels
     * @param top    the top margin in pixels
     * @param right  the right margin in pixels
     * @param bottom the bottom margin in pixels
     */
    void setItemMargin(int left, int top, int right, int bottom);

    /**
     * Set margins for this ViewPager
     *
     * @param left  the left margin in pixels
     * @param right the right margin in pixels
     */
    void setScrollMargin(int left, int right);

    /**
     * The items.size() would be scale to item.size()*infiniteRatio in fact
     * size()将被扩展到item.size()无限迭代????
     *
     * @param infiniteRatio
     */
    void setInfiniteRatio(int infiniteRatio);
}

UltraViewPagerIndicator 指示器View

首先他是一个自定义View继承View实现了Viewpager监听的接口和IUltraIndicatorBuilder接口
首先在他的三个构造方法中添加了init() 初始化方法

 public UltraViewPagerIndicator(Context context) {
        super(context);
        init();
    }

    public UltraViewPagerIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public UltraViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

init();方法中初始化了两个画笔和默认指示器半径的代码

 private void init() {
        paintStroke = new Paint(Paint.ANTI_ALIAS_FLAG);
        //负责画空心
        paintStroke.setStyle(Paint.Style.STROKE);
        paintFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        //负责画实心
        paintFill.setStyle(Paint.Style.FILL);
        //默认的半径
        defaultRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_RADIUS, getResources().getDisplayMetrics());
    }

关键方法来了onDraw(),代码如下,逻辑也是很清晰了

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (viewPager == null || viewPager.getAdapter() == null)
            return;
        //获得viewpager页面的的数量
        final int count = ((UltraViewPagerAdapter) viewPager.getAdapter()).getRealCount();
        if (count == 0)
            return;

        int longSize;
        int shortSize;

        int longPaddingBefore;
        int longPaddingAfter;

        int shortPaddingBefore;
        int shortPaddingAfter;
        //判断如果是水平方向的指示器
        if (orientation == UltraViewPager.Orientation.HORIZONTAL) {
            //由于是水平方向上的所以longSize为宽 shortSize为高
            longSize = viewPager.getWidth();
            shortSize = viewPager.getHeight();
            longPaddingBefore = getPaddingLeft() + marginLeft;
            longPaddingAfter = getPaddingRight() + marginRight;
            shortPaddingBefore = getPaddingTop() + marginTop;
            shortPaddingAfter = (int) paintStroke.getStrokeWidth() + getPaddingBottom() + marginBottom;
        } else {
            longSize = viewPager.getHeight();
            shortSize = viewPager.getWidth();
            longPaddingBefore = getPaddingTop() + marginTop;
            longPaddingAfter = (int) paintStroke.getStrokeWidth() + getPaddingBottom() + marginBottom;
            shortPaddingBefore = getPaddingLeft() + marginLeft;
            shortPaddingAfter = getPaddingRight() + marginRight;
        }

        final float itemWidth = getItemWidth();
        final int widthRatio = isDrawResIndicator() ? 1 : 2; //bitmap resource X1 : circle  X2
        if (indicatorPadding == 0) {
            indicatorPadding = (int) itemWidth;
        }

        float shortOffset = shortPaddingBefore;
        float longOffset = longPaddingBefore;

        final float indicatorLength = (count - 1) * (itemWidth * widthRatio + indicatorPadding);

        final int horizontalGravityMask = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
        final int verticalGravityMask = gravity & Gravity.VERTICAL_GRAVITY_MASK;
        switch (horizontalGravityMask) {
            case Gravity.CENTER_HORIZONTAL:
                longOffset = (longSize - longPaddingBefore - longPaddingAfter - indicatorLength) / 2.0f;
                break;
            case Gravity.RIGHT:
                if (orientation == UltraViewPager.Orientation.HORIZONTAL) {
                    longOffset = longSize - longPaddingAfter - indicatorLength - itemWidth;
                }
                if (orientation == UltraViewPager.Orientation.VERTICAL) {
                    shortOffset = shortSize - shortPaddingAfter - itemWidth;
                }
                break;
            case Gravity.LEFT:
                longOffset += itemWidth;
            default:
                break;
        }

        switch (verticalGravityMask) {
            case Gravity.CENTER_VERTICAL:
                shortOffset = (shortSize - shortPaddingAfter - shortPaddingBefore - itemWidth) / 2;
                break;
            case Gravity.BOTTOM:
                if (orientation == UltraViewPager.Orientation.HORIZONTAL) {
                    shortOffset = shortSize - shortPaddingAfter - getItemHeight();
                }
                if (orientation == UltraViewPager.Orientation.VERTICAL) {
                    longOffset = longSize - longPaddingAfter - indicatorLength;
                }
                break;
            case Gravity.TOP:
                shortOffset += itemWidth;
            default:
                break;
        }

        if (horizontalGravityMask == Gravity.CENTER_HORIZONTAL && verticalGravityMask == Gravity.CENTER_VERTICAL) {
            shortOffset = (shortSize - shortPaddingAfter - shortPaddingBefore - itemWidth) / 2;
        }

        float dX;
        float dY;

        float pageFillRadius = radius;
        if (paintStroke.getStrokeWidth() > 0) {
            pageFillRadius -= paintStroke.getStrokeWidth() / 2.0f; //TODO may not/2
        }

        //Draw stroked circles
        for (int iLoop = 0; iLoop < count; iLoop++) {
            float drawLong = longOffset + (iLoop * (itemWidth * widthRatio + indicatorPadding));

            if (orientation == UltraViewPager.Orientation.HORIZONTAL) {
                dX = drawLong;
                dY = shortOffset;
            } else {
                dX = shortOffset;
                dY = drawLong;
            }

            if (isDrawResIndicator()) {
                if (iLoop == viewPager.getCurrentItem())
                    continue;
                canvas.drawBitmap(normalBitmap, dX, dY, paintFill);
            } else {
                // Only paint fill if not completely transparent
                if (paintFill.getAlpha() > 0) {
                    paintFill.setColor(normalColor);
                    canvas.drawCircle(dX, dY, pageFillRadius, paintFill);
                }

                // Only paint stroke if a stroke width was non-zero
                if (pageFillRadius != radius) {
                    canvas.drawCircle(dX, dY, radius, paintStroke);
                }
            }
        }

        //Draw the filled circle according to the current scroll
        float cx = (viewPager.getCurrentItem()) * (itemWidth * widthRatio + indicatorPadding);
        if (animateIndicator)
            cx += pageOffset * itemWidth;
        if (orientation == UltraViewPager.Orientation.HORIZONTAL) {
            dX = longOffset + cx;
            dY = shortOffset;
        } else {
            dX = shortOffset;
            dY = longOffset + cx;
        }

        if (isDrawResIndicator()) {
            canvas.drawBitmap(focusBitmap, dX, dY, paintStroke);
        } else {
            paintFill.setColor(focusColor);
            canvas.drawCircle(dX, dY, radius, paintFill);
        }
    }

UltraViewPagerAdapter

UltraViewPagerAdapter继承了PagerAdapter

//根据是否是无线循环的来返回当前的count
 @Override
    public int getCount() {
        int count;
        if (enableLoop) {
            if (adapter.getCount() == 0) {
                count = 0;
            } else {
                count = adapter.getCount() * infiniteRatio;
            }
        } else {
            count = adapter.getCount();
        }
        return count;
    }
@Override //此方法是实例化一个对象 position是要实例化的位置
    public Object instantiateItem(ViewGroup container, int position) {
        int realPosition = position;
        //TODO
        if (enableLoop && adapter.getCount() != 0) {
            realPosition = position % adapter.getCount();
        }

        Object item = adapter.instantiateItem(container, realPosition);
        //TODO
        View childView = null;
        if (item instanceof View)
            childView = (View) item;
        if (item instanceof RecyclerView.ViewHolder)
            childView = ((RecyclerView.ViewHolder) item).itemView;

        ViewPager viewPager = (ViewPager) container;
        int childCount = viewPager.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = viewPager.getChildAt(i);
            if (isViewFromObject(child, item)) { //判断是否已经关联上了
                viewArray.put(realPosition, child);
                break;
            }
        }

        if (isEnableMultiScr()) {
            if (scrWidth == 0) {
                scrWidth = container.getResources().getDisplayMetrics().widthPixels;
            }
            RelativeLayout relativeLayout = new RelativeLayout(container.getContext());

            if (childView.getLayoutParams() != null) {
                RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                        (int) (scrWidth * multiScrRatio),
                        ViewGroup.LayoutParams.MATCH_PARENT);

                layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
                childView.setLayoutParams(layoutParams);
            }

            container.removeView(childView);
            relativeLayout.addView(childView);

            container.addView(relativeLayout);
            return relativeLayout;
        }

        return item;
    }

UltraViewPager

UltraViewPager继承了RelativeLayout 实现了IUltraViewPagerFeature的所有方法,也是在XML文件中添加控件的View

开始是依旧初始化画笔和初始化VIEW

  public UltraViewPager(Context context) {
        super(context);
        size = new Point();
        maxSize = new Point();
        initView();
    }

    public UltraViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        size = new Point();
        maxSize = new Point();
        initView();
        initView(context, attrs);
    }

    public UltraViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        size = new Point();
        maxSize = new Point();
        initView();
    }

初始化方法根据版本来设置viewpager的id的不同方法

  private void initView() {
        viewPager = new UltraViewPagerView(getContext());
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            viewPager.setId(viewPager.hashCode());
        } else {
            viewPager.setId(View.generateViewId());
        }

        addView(viewPager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }

UltraViewPagerView

UltraViewPagerView继承自ViewPager

private void init(Context context, AttributeSet attrs) {
        setClipChildren(false);
        setOverScrollMode(OVER_SCROLL_NEVER); //设置不可以滚出这个视图
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值