自定义控件之无限循环自动banner :AutoBanner

313 阅读1分钟

无限循环banner实现过程中还是遇到很多坑的:主要有向后滑动白屏,只有两个page时白屏.最近一一解决了上述问题,并对其原因进行了探究,在此记录,希望能对初学者有所帮助.

/**

  • 关于无限循环banner,实现:
  • 1.继承ViewPager,实现滑动翻页
  • 2.通过重写PagerAdapter的getCount return Integer.MAX_VALUE,实现无限滑动
  • 3.Observable.interval结合setCurrentItem()实现自动翻页
  • 4.处理点击事件,自动滑动的暂停(MotionEvent.ACTION_MOVE)与启动(MotionEvent.ACTION_UP)

*注意:1.instantiateItem中view复用

  • 2.destroyItem中不做任何处理
  • 3.只有一个或者两个item时的特殊处理
  • @author fengjingchao */

代码:

 public class AutoBanner extends ViewPager {

  private int mCurrentPage;
  private Disposable mViewPagerSubscribe;
  private OnItemClickListener listener;
  private ArrayList<ScanEntity.BannersBean> urls;
  private Context mContext;
  private boolean isAutoPlay = true;
  private int delay;
  private int period;

  public AutoBanner(@NonNull Context context) {
    this(context, null);
  }

  public AutoBanner(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

  private void init(Context context, AttributeSet attrs) {
    TypedArray b = context.obtainStyledAttributes(attrs, R.styleable.AutoBanner);
    delay = b.getInt(R.styleable.AutoBanner_delay, 3000);
    period = b.getInt(R.styleable.AutoBanner_delay, 2000);
    mContext = context;
  }

  public interface OnItemClickListener {

    void onItemClick(int position);
  }

  /**
   * 为banner设置数据
   *
   * @param datas
   */
  public void setUrls(List<ScanEntity.BannersBean> datas) {

    if (datas == null || datas.size() == 0) {
      return;
    }
    if (datas.size() <= 1) {
      this.isAutoPlay = false;
    }
    urls = new ArrayList<>();
    //size==2时,由于前后两个相同,导致第三个instantiateItem时第一个被remove掉,会出现白屏
    if (datas.size() == 2) {
      urls.addAll(datas);
      urls.addAll(datas);
    } else {
      urls.addAll(datas);
    }

    ArrayList<ImageView> imageViews = new ArrayList<>();

    for (int i = 0; i < urls.size(); i++) {
      ImageView imageView = new ImageView(mContext);

      RequestOptions requestOptions = new RequestOptions().centerCrop();
      Glide.with(mContext).load(urls.get(i).getUrl()).apply(requestOptions).into(imageView);
      imageViews.add(imageView);
    }

    setAdapter(new ImageAdapter(imageViews));

    addOnPageChangeListener(new OnPageChangeListener() {
      @Override
      public void onPageScrolled(int i, float v, int i1) {

      }

      @Override
      public void onPageSelected(int position) {
        mCurrentPage = position;
      }

      @Override
      public void onPageScrollStateChanged(int i) {

      }
    });

    setCurrentItem(urls.size() * 50);
    resume();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        resume();
        break;
      case MotionEvent.ACTION_MOVE:
        stop();
        break;
    }
    return super.onTouchEvent(event);
  }

  public void stop() {
    if (mViewPagerSubscribe == null) {
      return;
    }
    if (!mViewPagerSubscribe.isDisposed()) {
      mViewPagerSubscribe.dispose();
    }
  }

  public void resume() {
    boolean isRunning = mViewPagerSubscribe != null && !mViewPagerSubscribe.isDisposed();
    if (!isAutoPlay || isRunning) {
      return;
    }
    mViewPagerSubscribe = Observable.interval(delay, period, TimeUnit.MILLISECONDS)
        .subscribeOn(AndroidSchedulers.mainThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<Long>() {
          @Override
          public void accept(Long aLong) throws Exception {
            // 进行轮播操作
            if (urls != null && urls.size() > 0) {
              mCurrentPage++;
              setCurrentItem(mCurrentPage);
            }
          }

        });


  }

  /**
   * 为banner item设置点击监听
   *
   * @param listener
   */
  public void setOnItemClickListener(OnItemClickListener listener) {
    this.listener = listener;
  }


  private class ImageAdapter extends PagerAdapter {

    private final int childCount;

    private ArrayList<ImageView> imageViews;

    public ImageAdapter(ArrayList<ImageView> imageViews) {
      this.imageViews = imageViews;
      childCount = imageViews.size();
    }

    @Override
    public int getCount() {
      //设置成最大,使用户看不到边界
      if (imageViews == null || imageViews.size() <= 1) {
        return imageViews.size();
      }
      return Integer.MAX_VALUE;
    }


    @Override
    public Object instantiateItem(ViewGroup container, final int position) {
      //重复利用view
      ImageView imageView = imageViews.get(position % childCount);
      imageView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
          if (listener != null) {
            listener.onItemClick(position % childCount);
          }
        }
      });
      ViewParent parent = imageView.getParent();//parent == container
      if (parent != null) {
        ViewGroup viewParent = (ViewGroup) parent;
        viewParent.removeView(imageView);
      }
      container.addView(imageView);
      return imageView;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
      return view == o;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
      //对于无限循环的viewpager ,view是需要被循环利用的,滑动一个循环后,container中的childcount将固定为最大值(即view的个数),再滑动的话也不会增加,
      // 所以不需要通过removeView来释放,removeView的话反而可能会将刚instantiateItem的view移除,导致出现白屏
//      container.removeView((View)object);
    }
  }
}

attr.xml

<resources>
    <declare-styleable name="AutoBanner">
        <attr name="delay" format="integer"></attr>
        <attr name="period" format="integer"></attr>
    </declare-styleable>

</resources>

layout.xml

 <com.citybank.test.main.weight.AutoBanner
      android:id="@+id/banner"
      android:layout_width="match_parent"
      android:layout_height="@dimen/dp_200"
      app:delay="3000"
      app:period="2000" />