Android自定义带动画无限自动轮播的Banner控件

显示的效果请往下看,先说一下需求,可以自动轮播,按下停止轮播,松手开始轮播,不可见时停止轮播,自动轮播时带动画,手动滑动时不带动画,点击时要有水波纹效果,下拉刷新回到第一页,页面不能卡顿等等。参考了一些网上的想法,结合自己的认知,总结如下。


自定义BannerView控件

为了以后开发的方便,这里将Banner封装成一个控件来使用,以后就可以直接在布局里引用。

做这种轮播效果一般采用的都是ViewPager,所以这个控件也是对ViewPager的封装,为了解耦,这里没有把适配器放在BannerView,只是提供了基类适配器和接口,使用很方便。

讲一下具体如何实现封装:
第一步:自定义控件,实现构造,初始化属性和视图。

  • 基于需求,先定义了页面边距,主页面占比,缩放比例,轮播时长,是否动画轮播等等属性,通过引用attrs的方式,在布局里赋值,并且提供set方法在代码里设置。
   private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BannerView);
        pageMargin = (int) a.getDimension(R.styleable.BannerView_bannerPageMargin, pageMargin);
        pagePercent = a.getFloat(R.styleable.BannerView_bannerPagePercent, pagePercent);
        scaleMin = a.getFloat(R.styleable.BannerView_bannerPageScale, scaleMin);
        alphaMin = a.getFloat(R.styleable.BannerView_bannerPageAlpha, alphaMin);
        ...
        a.recycle();
    }
  • 定义了基本属性后,将ViewPager视图填充到控件容器里,ViewPager布局里需要使用clipChildren属性来控制系统绘制View的范围,在ViewPager和它的父容器里都设置clipChildren=false。这样指定了主页面占比后,左右两边的Page就会进行绘制。
    private void initView() {
        mRootView = LayoutInflater.from(getContext()).inflate(R.layout.banner_view, this);
        mViewPager = (ViewPager) mRootView.findViewById(R.id.viewPager);
        LayoutParams params = (LayoutParams) mViewPager.getLayoutParams();
        params.width = (int) (getScreenWidth() * pagePercent);
        params.gravity = Gravity.CENTER;
        mViewPager.setLayoutParams(params);
        mViewPager.setPageMargin(pageMargin);
        mViewPager.setPageTransformer(false, new BannerPageTransformer());
        mViewPager.setOffscreenPageLimit(5);
        // 自动轮播任务
        mScrollTask = new AutoScrollTask();
        // 如果动画轮播
        if (isAnimScroll) {
            setAnimationScroll((int) mAnimDuration);
        }
    }

第二步:滑动动画实现

  • 众所周知,谷歌已经提供了ViewPager的滑动动画设置接口setPageTransformer(),并且他自己也实现了三种基本的滑动滑动。需要自定义动画时只需实现ViewPager.PageTransformer接口,并实现transformPage()方法。

  • 设计稿中的动画要求是,滑动时左侧缩小,主页随着滑动百分比缩小,右侧随着滑动百分比放大。transformPage方法中提供了主页所在的position=0.0,左侧位置百分比position<0,右侧位置百分比position>0,通过这个position可以动态计算出各个页面的缩放比。

    public void transformPage(View page, float position) {
       // 不同位置的缩放和透明度
       float scale = (position < 0)
                ? ((1 - scaleMin) * position + 1)
                : ((scaleMin - 1) * position + 1);
       float alpha = (position < 0)
                ? ((1 - alphaMin) * position + 1)
                : ((alphaMin - 1) * position + 1);
       // 保持左右两边的图片位置中心
       if (position < 0) {
            ViewCompat.setPivotX(page, page.getWidth());
            ViewCompat.setPivotY(page, page.getHeight() / 2);
        } else {
            ViewCompat.setPivotX(page, 0);
            ViewCompat.setPivotY(page, page.getHeight() / 2);
        }
        Log.d(TAG, "transformPage: scale=" + scale);
        ViewCompat.setScaleX(page, scale);
        ViewCompat.setScaleY(page, scale);
        ViewCompat.setAlpha(page, Math.abs(alpha));
    }

第三步:无限自动轮播和轮播动画处理

  • 无限轮播可用线程池,定时器,Handler等等实现,这里采用最简单的Handler实现。首先自定义一个轮播任务,实现Runnable接口,在run方法里使用Handler发送延时消息来不停轮播,并且提供start方法和stop方法开启和停止轮播。在初始化视图完毕时,开启轮播。
   @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // 视图初始化完毕,开始轮播任务
        if (mScrollTask == null) mScrollTask = new AutoScrollTask();
        if (isAutoScroll) startAutoScroll();
    }
  • ViewPager滑动是没有延时的,谷歌也没有提供具体的接口去实现延时滑动。所以这里使用反射去重新设置ViewPager的滑动世间。自动轮播时,需求动画延时滑动,但是手动滑动时需要原生的滑动,所以根据使用时间差和滑动时间来控制自动和手动滑动。
 public void startScroll(int startX, int startY, int dx,
                                        int dy, int duration) {
      // 如果手动滚动,则加速滚动
         // TODO 使用这种设置极不稳定,需要抽离
         if (System.currentTimeMillis() - mRecentTouchTime > mScrollDuration && isAnimScroll) {
             // 动画滑动
             duration = during;
         } else {
             // 手势滚动
             duration /= 2;

         }
         super.startScroll(startX, startY, dx, dy, duration);
     }

BannerView的基类适配器封装

  • BannerView封装的是ViewPager,所以基类适配器BannerBaseAdapter封装的也是PagerAdapter,由于适配器涉及到数据和视图,所以基类里将所有都封装好,只留数据和视图留给子类去实现。除此之外,页面的点击,按下和抬起也在适配器实现并通过接口暴露出来。
  • 实现父类适配后,只需要指定数据类型和实现展示视图转换数据的方法,加载完数据之后,通过setData方法来重新更新数据即可。
private class BannerAdapter extends BannerBaseAdapter {
        public BannerAdapter(Context context) {
            super(context);
        }
        @Override
        protected int getLayoutResID() {
            return R.layout.item_banner;
        }
        @Override
        protected void convert(View convertView, BannerBean data) {
            setImage(R.id.pageImage, data.imageRes);
            setText(R.id.pageText, data.title);
        }
  }

使用教程

  • 拷贝BannerView全路径到布局里,使用attrs指定属性
 
  • 在代码中设置适配器和设置页面的触摸监听,初始化数据之后,更新数据
  final BannerView bannerView = (BannerView) findViewById(R.id.bannerView);
  bannerView.setAdapter(mAdapter = new BannerAdapter(this));
  initData();  // 初始化数据
  mAdapter.setData(mDatas);
  mAdapter.setOnPageTouchListener(...);

Banner相关请查看最新开源项目BannerView

欢迎大家访问我的,博客和GitHub。

你可能感兴趣的:(Android自定义带动画无限自动轮播的Banner控件)