view

View-ViewPager(研究setAdapter、setC

2020-04-14  本文已影响0人  isLJli

1.setAdapter()方法

// 我们的FragmentPagerAdapter、FragmentStatePagerAdapter就是继承PagerAdapter的。
      mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
          @NonNull
          @Override
          public Fragment getItem(int position) {
              return ItemFragment.newInstance(items[position]);
          }

          @Override
          public int getCount() {
              return items.length;
          }
      });

看看其setAdapter的源码:主要是清理前PagerAdapter,并为这个PagerAdapter做一些配置

public void setAdapter(PagerAdapter adapter) {
  //1.如果已经设置过PagerAdapter,即mAdapter != null,
  // 则做一些清理工作
  if (mAdapter != null) {
      //2.清除观察者
      mAdapter.setViewPagerObserver(null);
      //3.回调startUpdate函数,告诉PagerAdapter开始更新要显示的页面
      mAdapter.startUpdate(this);
      //4.如果之前保存有页面,则将之前所有的页面destroy掉
      for (int i = 0; i < mItems.size(); i++) {
          final ItemInfo ii = mItems.get(i);
          mAdapter.destroyItem(this, ii.position, ii.object);
      }
      //5.回调finishUpdate,告诉PagerAdapter结束更新
      mAdapter.finishUpdate(this);
      //6.将所有的页面清除
      mItems.clear();
      //7.将所有的非Decor View移除,即将页面移除
      removeNonDecorViews();
      //8.当前的显示页面重置到第一个
      mCurItem = 0;
      //9.滑动重置到(0,0)位置
      scrollTo(0, 0);
  }

  //10.保存上一次的PagerAdapter
  final PagerAdapter oldAdapter = mAdapter;
  //11.设置mAdapter为新的PagerAdapter
  mAdapter = adapter;
  //12.设置期望的适配器中的页面数量为0个
  mExpectedAdapterCount = 0;
  //13.如果设置的PagerAdapter不为null
  if (mAdapter != null) {
      //14.确保观察者不为null,观察者主要是用于监视数据源的内容发生变化
      if (mObserver == null) {
          mObserver = new PagerObserver();
      }
      //15.将观察者设置到PagerAdapter中
      mAdapter.setViewPagerObserver(mObserver);
      mPopulatePending = false;
      //16.保存上一次是否是第一次Layout
      final boolean wasFirstLayout = mFirstLayout;
      //17.设定当前为第一次Layout
      mFirstLayout = true;
      //18.更新期望的数据源中页面个数
      mExpectedAdapterCount = mAdapter.getCount();
      //19.如果有数据需要恢复
      if (mRestoredCurItem >= 0) {
          //20.回调PagerAdapter的restoreState函数
          mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
          setCurrentItemInternal(mRestoredCurItem, false, true);
          //21.标记无需再恢复
          mRestoredCurItem = -1;
          mRestoredAdapterState = null;
          mRestoredClassLoader = null;
      } else if (!wasFirstLayout) {//如果在此之前不是第一次Layout
          //22.由于ViewPager并不是将所有页面作为子View,
          // 而是最多缓存用户指定缓存个数*2(左右两边,可能左边或右边没有那么多页面)
          //因此需要创建和销毁页面,populate主要工作就是这些
          populate();
      } else {
          //23.重新布局(Layout)
          requestLayout();
      }
  }
  //24.如果PagerAdapter发生变化,并且设置了OnAdapterChangeListener监听器
  // 则回调OnAdapterChangeListener的onAdapterChanged函数
  if (mAdapterChangeListener != null && oldAdapter != adapter) {
      mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
  }
}

在setAdapter()中populate()方法来创建和销毁itemView,这个方法又会调用带一个需要定位显示页面的参数populate(int newCurrentItem)

1.2 populate(int newCurrentItem)方法

这个方法主要是创建和销毁,并缓存页面。
ViewPager里面有多少界面都不会卡,会不断的去销毁和创建页面,默认不光会创建当前页面,还会创建相邻的offscreenPageLimit页面,offscreenPageLimit代表相邻页面的个数,可以由用户通过setOffscreenPageLimit()方法指定,默认是1。而创建会调用PagerAdapter的instantiateItem()方法有一个过时了我们不要用过时的,而销毁会调用PagerAdapter的destroyItem()。

image.png
void populate(int newCurrentItem) {
  ItemInfo oldCurInfo = null;
  if (mCurItem != newCurrentItem) {
      oldCurInfo = infoForPosition(mCurItem);
      mCurItem = newCurrentItem;
  }

  if (mAdapter == null) {
      //对子View的绘制顺序进行排序,优先绘制Decor View
      //再按照position从小到大排序
      sortChildDrawingOrder();
      return;
  }

  //如果我们正在等待populate,那么在用户手指抬起切换到新的位置期间应该推迟创建子View,
  // 直到滚动到最终位置再去创建,以免在这个期间出现差错
  if (mPopulatePending) {
      if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
      //对子View的绘制顺序进行排序,优先绘制Decor View
      //再按照position从小到大排序
      sortChildDrawingOrder();
      return;
  }

  //同样,在ViewPager没有attached到window之前,不要populate.
  // 这是因为如果我们在恢复View的层次结构之前进行populate,可能会与要恢复的内容有冲突
  if (getWindowToken() == null) {
      return;
  }
  //回调PagerAdapter的startUpdate函数,
  // 告诉PagerAdapter开始更新要显示的页面
  mAdapter.startUpdate(this);

  final int pageLimit = mOffscreenPageLimit;
  //确保起始位置大于等于0,如果用户设置了缓存页面数量,第一个页面为当前页面减去缓存页面数量
  final int startPos = Math.max(0, mCurItem - pageLimit);
  //保存数据源中的数据个数
  final int N = mAdapter.getCount();
  //确保最后的位置小于等于数据源中数据个数-1,
  // 如果用户设置了缓存页面数量,第一个页面为当前页面加缓存页面数量
  final int endPos = Math.min(N - 1, mCurItem + pageLimit);

  //判断用户是否增减了数据源的元素,如果增减了且没有调用notifyDataSetChanged,则抛出异常
  if (N != mExpectedAdapterCount) {
      //resName用于抛异常显示
      String resName;
      try {
          resName = getResources().getResourceName(getId());
      } catch (Resources.NotFoundException e) {
          resName = Integer.toHexString(getId());
      }
      throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
              " contents without calling PagerAdapter#notifyDataSetChanged!" +
              " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
              " Pager id: " + resName +
              " Pager class: " + getClass() +
              " Problematic adapter: " + mAdapter.getClass());
  }

  //定位到当前获焦的页面,如果没有的话,则添加一个
  int curIndex = -1;
  ItemInfo curItem = null;
  //遍历每个页面对应的ItemInfo,找出获焦页面
  for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
      final ItemInfo ii = mItems.get(curIndex);
      //找到当前页面对应的ItemInfo后,跳出循环
      if (ii.position >= mCurItem) {
          if (ii.position == mCurItem) curItem = ii;
          break;
      }
  }
  //如果没有找到获焦的页面,说明mItems列表里面没有保存获焦页面,
  // 需要将获焦页面加入到mItems里面
  if (curItem == null && N > 0) {
      curItem = addNewItem(mCurItem, curIndex);
  }

  //默认缓存当前页面的左右两边的页面,如果用户设定了缓存页面数量,
  // 则将当前页面两边都缓存用户指定的数量的页面
  //如果当前没有页面,则我们啥也不需要做
  if (curItem != null) {
      float extraWidthLeft = 0.f;
      //左边的页面
      int itemIndex = curIndex - 1;
      //如果当前页面左边有页面,则将左边页面对应的ItemInfo取出,否则左边页面的ItemInfo为null
      ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
      //保存显示区域的宽度
      final int clientWidth = getClientWidth();
      //算出左边页面需要的宽度,注意,这里的宽度是指实际宽度与可视区域宽度比例,
      // 即实际宽度=leftWidthNeeded*clientWidth
      final float leftWidthNeeded = clientWidth <= 0 ? 0 :
              2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
      //从当前页面左边第一个页面开始,左边的页面进行遍历
      for (int pos = mCurItem - 1; pos >= 0; pos--) {
          //如果左边的宽度超过了所需的宽度,并且当前当前页面位置比第一个缓存页面位置小
          //这说明这个页面需要Destroy掉
          if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
              //如果左边已经没有页面了,跳出循环
              if (ii == null) {
                  break;
              }
              //将当前页面destroy掉
              if (pos == ii.position && !ii.scrolling) {
                  mItems.remove(itemIndex);
                  //回调PagerAdapter的destroyItem
                  mAdapter.destroyItem(this, pos, ii.object);
                  if (DEBUG) {
                      Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                              " view: " + ((View) ii.object));
                  }
                  //由于mItems删除了一个元素
                  //需要将索引减一
                  itemIndex--;
                  curIndex--;
                  ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
              }
          } else if (ii != null && pos == ii.position) {
              //如果当前位置是需要缓存的位置,并且这个位置上的页面已经存在
              //则将左边宽度加上当前位置的页面
              extraWidthLeft += ii.widthFactor;
              //mItems往左遍历
              itemIndex--;
              //ii设置为当前遍历的页面的左边一个页面
              ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          } else {//如果当前位置是需要缓存,并且这个位置没有页面
              //需要添加一个ItemInfo,而addNewItem是通过PagerAdapter的instantiateItem获取对象
              ii = addNewItem(pos, itemIndex + 1);
              //将左边宽度加上当前位置的页面
              extraWidthLeft += ii.widthFactor;
              //由于新加了一个元素,当前的索引号需要加1
              curIndex++;
              //ii设置为当前遍历的页面的左边一个页面
              ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          }
      }
      //同理,右边需要添加缓存的页面
      //......

     // 省略右边添加缓存页面代码  

     //......

      calculatePageOffsets(curItem, curIndex, oldCurInfo);
  }

  if (DEBUG) {
      Log.i(TAG, "Current page list:");
      for (int i = 0; i < mItems.size(); i++) {
          Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
      }
  }
  //回调PagerAdapter的setPrimaryItem,告诉PagerAdapter当前显示的页面
  mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
  //回调PagerAdapter的finishUpdate,告诉PagerAdapter页面更新结束
  mAdapter.finishUpdate(this);


  //检查页面的宽度是否测量,如果页面的LayoutParams数据没有设定,则去重新设定好
  final int childCount = getChildCount();
  for (int i = 0; i < childCount; i++) {
      final View child = getChildAt(i);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      lp.childIndex = i;
      if (!lp.isDecor && lp.widthFactor == 0.f) {
          // 0 means requery the adapter for this, it doesn't have a valid width.
          final ItemInfo ii = infoForChild(child);
          if (ii != null) {
              lp.widthFactor = ii.widthFactor;
              lp.position = ii.position;
          }
      }
  }
  //重新对页面排序
  sortChildDrawingOrder();
  //如果ViewPager被设定为可获焦的,则将当前显示的页面设定为获焦
  if (hasFocus()) {
      View currentFocused = findFocus();
      ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
      if (ii == null || ii.position != mCurItem) {
          for (int i = 0; i < getChildCount(); i++) {
              View child = getChildAt(i);
              ii = infoForChild(child);
              if (ii != null && ii.position == mCurItem) {
                  if (child.requestFocus(View.FOCUS_FORWARD)) {
                      break;
                  }
              }
          }
      }
  }
}

2.setCurrentItemInternal()

//此方法跳到相应的页面
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
      if (mAdapter == null || mAdapter.getCount() <= 0) {
          setScrollingCacheEnabled(false);
          return;
      }
      if (!always && mCurItem == item && mItems.size() != 0) {
          setScrollingCacheEnabled(false);
          return;
      }

      if (item < 0) {
          item = 0;
      } else if (item >= mAdapter.getCount()) {
          item = mAdapter.getCount() - 1;
      }
      final int pageLimit = mOffscreenPageLimit;
      if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
          // We are doing a jump by more than one page.  To avoid
          // glitches, we want to keep all current pages in the view
          // until the scroll ends.
          for (int i = 0; i < mItems.size(); i++) {
              mItems.get(i).scrolling = true;
          }
      }
      final boolean dispatchSelected = mCurItem != item;

      if (mFirstLayout) {
          // We don't have any idea how big we are yet and shouldn't have any pages either.
          // Just set things up and let the pending layout handle things.
          mCurItem = item;
          if (dispatchSelected) {
              dispatchOnPageSelected(item);
          }
          requestLayout();
      } else {
         //创建item位置的页面
          populate(item);
         //滑动到item页面
          scrollToItem(item, smoothScroll, velocity, dispatchSelected);
      }
  }
private void scrollToItem(int item, boolean smoothScroll, int velocity,
          boolean dispatchSelected) {
      final ItemInfo curInfo = infoForPosition(item);
      int destX = 0;
      if (curInfo != null) {
          final int width = getClientWidth();
          destX = (int) (width * Math.max(mFirstOffset,
                  Math.min(curInfo.offset, mLastOffset)));
      }
      if (smoothScroll) {
         //调用这个滑动方法
          smoothScrollTo(destX, 0, velocity);
          if (dispatchSelected) {
              dispatchOnPageSelected(item);
          }
      } else {
          if (dispatchSelected) {
              dispatchOnPageSelected(item);
          }
          completeScroll(false);
          scrollTo(destX, 0);
          pageScrolled(destX);
      }
  }
void smoothScrollTo(int x, int y, int velocity) {
      if (getChildCount() == 0) {
          // Nothing to do.
          setScrollingCacheEnabled(false);
          return;
      }

      int sx;
      boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
      if (wasScrolling) {
          // We're in the middle of a previously initiated scrolling. Check to see
          // whether that scrolling has actually started (if we always call getStartX
          // we can get a stale value from the scroller if it hadn't yet had its first
          // computeScrollOffset call) to decide what is the current scrolling position.
          sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
          // And abort the current scrolling.
          mScroller.abortAnimation();
          setScrollingCacheEnabled(false);
      } else {
          sx = getScrollX();
      }
      int sy = getScrollY();
      int dx = x - sx;
      int dy = y - sy;
      if (dx == 0 && dy == 0) {
          completeScroll(false);
          // 又是这个方法,又是你我认识
          populate();
          setScrollState(SCROLL_STATE_IDLE);
          return;
      }

      setScrollingCacheEnabled(true);
      setScrollState(SCROLL_STATE_SETTLING);

      final int width = getClientWidth();
      final int halfWidth = width / 2;
      final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
      final float distance = halfWidth + halfWidth *
              distanceInfluenceForSnapDuration(distanceRatio);
      // 页面切换的持续时间
      int duration;
      velocity = Math.abs(velocity);
      if (velocity > 0) {
          duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
      } else {
          final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
          final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
          duration = (int) ((pageDelta + 1) * 100);
      }
      // 切换页面的时间  
      duration = Math.min(duration, MAX_SETTLE_DURATION);

      // Reset the "scroll started" flag. It will be flipped to true in all places
      // where we call computeScrollOffset().
      mIsScrollStarted = false;
      // 切换页面最终会调用Scroller的startScroll()方法
      mScroller.startScroll(sx, sy, dx, dy, duration);
      ViewCompat.postInvalidateOnAnimation(this);
  }

创建ItemView内部方法: mAdapter.instantiateItem(this, position)
销毁ItemView内部方法: mAdapter.destroyItem(this, pos, ii.object);
切换页面执行动:mScroller.startScroll(sx, sy, dx, dy, duration);

实现一个图片轮播

1.我们先自定义一个viewPager和PagerAdapter。这个PagerAdapter能返回一个ImageView作为itemView(通过adapter返回自定义View),同时这个viewPager通过反射改变图片切换的速度。
2.之后把我们自定义的ViewPager和指示内容布局,并通过inflate()加载到继承RelativeLayout自定义View-BannerView中。通过给viewpager监听滑动来显示描述和点指示

使用

用户端,只需要加载BannerView布局,并设置好参数。

<declare-styleable name="BannerView">
      <!-- 点选中的颜色值 -->
      <attr name="dotIndicatorFocus" format="color|reference" />
      <!-- 点默认的颜色值 -->
      <attr name="dotIndicatorNormal" format="color|reference" />
      <!-- 点的大小 -->
      <attr name="dotSize" format="dimension"/>
      <!-- 点的距离 -->
      <attr name="dotDistance" format="dimension"/>
      <!-- 点的位置 -->
      <attr name="dotGravity" format="enum" >
          <enum name="center" value="0" />
          <enum name="left" value="1" />
          <enum name="right" value="-1" />
      </attr>
      <!-- 底部颜色 -->
      <attr name="bottomColor" format="color" />
      <!-- 定义宽高比 -->
      <attr name="widthProportion" format="float" />
      <attr name="heigthProportion" format="float" />
  </declare-styleable>
<com.haiming.myapplication.banner.BannerView
      android:layout_width="match_parent"
      android:layout_height="0dp"
      android:id="@+id/banner_view"
      app:dotSize="8dp"
      app:dotDistance="10dp"
      app:dotGravity="right"
      app:bottomColor="@color/colorAccent"
      app:dotIndicatorFocus="@color/red"
      app:dotIndicatorNormal="@color/white"
      app:widthProportion="7"
      app:heigthProportion="6"
      />
public class MainActivity extends AppCompatActivity {

  private BannerView mBannerView;

  private String[] url = {
          "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357897&di=87c5801dc8f155c54b137a2288585943&imgtype=0&src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Fd009b3de9c82d1587e249850820a19d8bd3e42a9.jpg",
          "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357896&di=3e48f71c2d4b942e7a292e9ee228ba68&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Fd62a6059252dd42a1c362a29033b5bb5c9eab870.jpg",
          "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357895&di=42e050010f27198d7a831370a854ff4c&imgtype=0&src=http%3A%2F%2Fbig5.wallcoo.com%2Fanimal%2Ffly_and_freedom%2Fimages%2F0Vol_096_DY164.jpg",
          "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583608357895&di=64a4126a298344bdd2d1ae9e6f469632&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg"
  };

  private String[] desc = {
          "游戏","狮子","鸟","美景"
  };

  public BannerAdapter<String> mBannerAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.banner_layout);

      mBannerView= findViewById(R.id.banner_view);

      initBanner();
  }

  private void initBanner() {

      if(mBannerAdapter == null){
          mBannerAdapter = new BannerAdapter<String>(Arrays.asList(url)) {
              @Override
              public View getView(int position, View convertView, List<String> dataList) {
                  ImageView bannerIv = null;
                  if(convertView == null){
                      bannerIv = new ImageView(MainActivity.this);
                  }else {
                      bannerIv= (ImageView) convertView;
                  }
                  int curPosition = position%getCount();
                 //利用第三方的工具加载图片 glide
                  String  path = dataList.get(curPosition);
                  Glide.with(MainActivity.this)
                          .load(path)
                          .placeholder(R.mipmap.ic_launcher)
                          .into(bannerIv);
                  return bannerIv;
              }

              @Override
              public int getCount() {
                  return url.length;
              }
          };
      }
      mBannerAdapter.setDescription(Arrays.asList(desc));
      mBannerView.setAdapter(mBannerAdapter);
  }

}

自定义viewPager和PagerAdapter

public class BannerViewPager<T> extends ViewPager {

  //数据的资源
  private List<T> mDataList;
  //自定义adapter,主要是能自定义view
  private BannerAdapter mAdapter;

  //发送消息的Msg
  private final int MSG = 0X0010;
  //页面切换间隔时间
  private int mCurDownTime = 3500;
  //切换到指定的item,并重新发送消息
  private Handler handler = new Handler(){
      @Override
      public void handleMessage(@NonNull Message msg) {
          super.handleMessage(msg);
          //每隔多少秒切换下一张图片
          setCurrentItem((getCurrentItem()+1));
          startRoll();
      }
  };

  //继承Scroller,修改滑动时间
  private BannerScroller mScroller;
  //存放被销毁的View
  private List<View> mCurrentView;

  private Activity mActivity;

  //管理activity的生命周期
  Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new DefaultActivityLifecycleCallbacks() {

      @Override
      public void onActivityResumed(@NonNull Activity activity) {
          //是不是监听当前的activity的生命周期
          if(activity == getContext()) {
              //开启轮播
              handler.sendEmptyMessageDelayed(mCurDownTime, MSG);
          }
      }

      @Override
      public void onActivityPaused(@NonNull Activity activity) {
          //是不是监听当前的activity的生命周期
          if(activity == getContext()) {
              //停止轮播
              handler.removeMessages(MSG);
          }
      }

  };

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

  public BannerViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
      super(context, attrs);
      initView(context);
  }


  public void setDataList(List<T> mDataList) {
      this.mDataList = mDataList;
  }

  public List<T> getDataList() {
      return mDataList;
  }

  private void initView(Context context) {
      this.mActivity = (Activity) context;
      //通过反射设置Scroller成另一个BannerScroller
      try {
          Field field = ViewPager.class.getDeclaredField("mScroller");
          field.setAccessible(true);
          mScroller = new BannerScroller(context);
          field.set(this,mScroller);
      } catch (Exception e) {
          e.printStackTrace();
      }
      mCurrentView = new ArrayList<>();
  }

  //设置切换页面动画的持续时间
  public void setScrollerDuration(int scrollerDuration){
      mScroller.setDurationTime(scrollerDuration);
  }

  public void setAdapter(@Nullable BannerAdapter adapter) {
      //自定义加载View的adpter
      this.mAdapter=adapter;
      // BannerPagerAdapter继承PagerAdapter,可以实现自定义加载view
      setAdapter(new BannerPagerAdapter<T>(mDataList));
      //管理activity的生命周期
      mActivity.getApplication().registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
  }


  /**
   * 实现自动切换,发送消息
   */
  public void  startRoll(){
      //清除消息
      handler.removeMessages(MSG);
      //消息 延迟发送
      handler.sendEmptyMessageDelayed(MSG,mCurDownTime);
  }

  /**
   * 销毁handler,避免内存泄漏
   */
  @Override
  protected void onDetachedFromWindow() {
      super.onDetachedFromWindow();
      handler.removeMessages(MSG);
      handler=null;
      mActivity.getApplication().unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
  }



  /**
   * veiwpager设置适配器
   */
  private class BannerPagerAdapter<T> extends PagerAdapter{



      //数据的资源
      private List<T> mDataList;

      public BannerPagerAdapter(List<T> dataList){
          this.mDataList = dataList;
      }

      @Override
      public int getCount() {
          //为了实现无线循环 0-2的32次方
          return Integer.MAX_VALUE;
      }

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

      /**
       * 创建VeiwPager条目回调的方法
       * @param container
       * @param position
       * @return
       */
      @NonNull
      @Override
      public Object instantiateItem(@NonNull ViewGroup container, int position) {
          // Adapter设计模式 为了完全让用户自定义
         View bannerItemVeiw =mAdapter.getView(position,getConverst(),mDataList);
         //添加ViewPager里面
          container.addView(bannerItemVeiw);
          return bannerItemVeiw;
      }


      /**
       * 销毁条目回调的方法
       * @param container
       * @param position
       * @param object
       */
      @Override
      public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
          container.removeView((View) object);
          mCurrentView.add((View) object);
      }

      //获取复用界面
      private View getConverst() {
          for(int i=0;i<mCurrentView.size();i++){
              if(mCurrentView.get(i).getParent() == null){
                  return mCurrentView.get(i);
              }
          }
          return null;
      }

  }

}

写一个可自定义itemView的adapter

public abstract class BannerAdapter<T> {

  private List<T> mDataList;

  private List<String> mDescription;


  public BannerAdapter(List<T> dataList){
      this.mDataList = dataList;
  }

  /**
   * 根据位置获取viewpager的子view
   * @param position
   * @return
   */
  public abstract View getView(int position,View convertView,List<T> dataList) ;

  /**
   * 返回所有子view的数量
   * @return
   */
  public abstract int getCount();

  public List<T> getDataList() {
      return mDataList;
  }

  public void setDescription(List<String> mDescription) {
      this.mDescription = mDescription;
  }

  public List<String> getDescription() {
      return mDescription;
  }


}

更换页面滑动的速度

public class BannerScroller extends Scroller {

  //动画持续的时间
  private int mDurationTime = 850;

  public BannerScroller(Context context) {
      super(context);
  }

  public BannerScroller(Context context, Interpolator interpolator) {
      super(context, interpolator);
  }

  public BannerScroller(Context context, Interpolator interpolator, boolean flywheel) {
      super(context, interpolator, flywheel);

  }

  @Override
  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
      // 把持续时间传我们的方法
      super.startScroll(startX, startY, dx, dy, mDurationTime);
  }

  public void setDurationTime(int time){
      this.mDurationTime=time;
  }
}

写一个轮播的布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.haiming.myapplication.banner.BannerViewPager
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:id="@+id/banner_vp"
      />

  <RelativeLayout
      android:id="@+id/banner_bottom_view"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:padding="10dp"

      >
      <TextView
          android:id="@+id/banner_desc_tv"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="12sp"
          android:textColor="@color/white"
          />
      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_centerVertical="true"
          android:id="@+id/dot_container"
          android:orientation="horizontal"
          >

      </LinearLayout>

  </RelativeLayout>

</RelativeLayout>

自定义View加载我们上面的布局,并动态生成圆点,和viewpager的监听

/**
* if(mBannerAdapter == null){
*     mBannerAdapter = new BannerAdapter<String>(Arrays.asList(url)) {
*         @Override
*         public View getView(int position, View convertView, List<String> dataList) {
*             ImageView bannerIv = null;
*             if(convertView == null){
*                 bannerIv = new ImageView(MainActivity.this);
*             }else {
*                 bannerIv= (ImageView) convertView;
*             }
*             int curPosition = position%getCount();
*            //利用第三方的工具加载图片 glide
*             String  path = dataList.get(curPosition);
*             Glide.with(MainActivity.this)
*                     .load(path)
*                     .placeholder(R.mipmap.ic_launcher)
*                     .into(bannerIv);
*             return bannerIv;
*         }
*         @Override
*         public int getCount() {
*             return url.length;
*         }
*     };
* }
* mBannerView.setAdapter(mBannerAdapter);
*/
public class BannerView extends RelativeLayout {

  private Context mContext;
  //轮播viewpager
  private BannerViewPager mBannerViewPager;
  //轮播的描述
  private TextView descTextView;
  //点的容器,里面addView圆点
  private LinearLayout mDotContainerView;
  //从外界拿到BannerAdapter,传给viewPager
  private  BannerAdapter mAdapter;
  //描述
  private List<String> desc ;

  //底部的控件
  private RelativeLayout relativeLayout;
  //底部容器颜色默认透明
  private int mButtomColor = Color.TRANSPARENT;

  //自定义圆点的颜色
  private Drawable mFocusDrawable = new ColorDrawable(Color.RED);
  private Drawable mNormalDrawable = new ColorDrawable(Color.WHITE);
  //自定义圆点的显示位置,默认中间
  private int mDotGravity = 0;
  //自定义圆点的大小
  private int mDotSize = 8;
  //自定义圆点的间据
  private int mDotDistance = 8;

  //获取宽高比例
  private float mWidthProportion,mHeightProportion ;
  //接口实例
  private BannerItemClickListener mClickListener;


  public BannerView(Context context) {
      super(context,null);
      initView(context);
  }

  public BannerView(Context context, AttributeSet attrs) {
      super(context, attrs,0);
      initView(context);
      initAttribute(attrs);
  }

  public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      initView(context);
      initAttribute(attrs);
  }


  private void initView(Context context) {
      //将这个布局加载这个view中
      inflate(context, R.layout.ui_banner,this);
      mBannerViewPager=findViewById(R.id.banner_vp);
      descTextView = findViewById(R.id.banner_desc_tv);
      mDotContainerView = findViewById(R.id.dot_container);
      relativeLayout= findViewById(R.id.banner_bottom_view);
      relativeLayout.post(new Runnable() {
          @Override
          public void run() {
              relativeLayout.setBackgroundColor(mButtomColor);
          }
      });
      this.mContext=context;
  }

  private void initAttribute(AttributeSet attrs) {
     TypedArray array = mContext.obtainStyledAttributes(attrs,R.styleable.BannerView);

     //获取点的位置
     mDotGravity = array.getInt(R.styleable.BannerView_dotGravity,0);
     //获取选择点的颜色
     mFocusDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
     if(mFocusDrawable == null){
         mFocusDrawable = new ColorDrawable(Color.RED);
     }
     Log.d("颜色","1");
     //获取未选中的颜色
      mNormalDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
      if(mNormalDrawable == null){
          mNormalDrawable = new ColorDrawable(Color.WHITE);
      }
      //获取点的大小
      mDotSize = (int) array.getDimension(R.styleable.BannerView_dotSize,dip2px(mDotSize));
      //获取点的间距
      mDotDistance = (int) array.getDimension(R.styleable.BannerView_dotDistance,dip2px(mDotDistance));
      //获取底部颜色
      mButtomColor = array.getColor(R.styleable.BannerView_bottomColor,mButtomColor);

      //获取宽高比例
      mWidthProportion = array.getFloat(R.styleable.BannerView_widthProportion,mWidthProportion);
      mHeightProportion = array.getFloat(R.styleable.BannerView_heigthProportion,mHeightProportion);

      array.recycle();
  }

  public void setAdapter(final BannerAdapter adapter){
      this.mAdapter=adapter;
      this.desc = adapter.getDescription();
      final int count = mAdapter.getCount();
      mBannerViewPager.setDataList(mAdapter.getDataList());
      mBannerViewPager.setAdapter(adapter);
      //初始化点的指示器,默认不显示
          relativeLayout.setVisibility(VISIBLE);
          initDotIndicator();
      mBannerViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
          @Override
          public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
              int curPosition = position%mAdapter.getCount();
              //改变圆点颜色
                  relativeLayout.setVisibility(VISIBLE);
                  pageSelect(curPosition);

              //显示描述,默认不显示
                  relativeLayout.setVisibility(VISIBLE);
                  if(desc != null && desc.size()==count){
                      descTextView.setText(desc.get(curPosition));
                  }

              if(mClickListener != null){
                  mClickListener.click(curPosition);
              }
          }

          @Override
          public void onPageSelected(int position) {

          }

          @Override
          public void onPageScrollStateChanged(int state) {

          }
      });

      //动态设置高度
      setPicHeight();
      //开始通过handler发送消息
      startRoll();
  }

  /**
   * 动态设置高度
   */
  private void setPicHeight() {
      //动态指定宽高,计算高度
      WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
      DisplayMetrics metrics = new DisplayMetrics();
      manager.getDefaultDisplay().getMetrics(metrics);
      int width = metrics.widthPixels;

      if(mWidthProportion == 0 || mHeightProportion ==0){
          return;
      }
      int heigth = (int) ((width*mHeightProportion)/mWidthProportion);

      Log.d("高度","heigth="+heigth+" width="+width+" mWidthProportion="+mWidthProportion+" mHeightProportion"+mHeightProportion);
      getLayoutParams().height=heigth;
  }

  /**
   * 页面切换的回调
   * @param position
   */
  private int mCurrentPosition =0;
  private void pageSelect(int position) {
      //把原来的位置颜色设置默认
      DotIndicatorView oldIndicatorView= (DotIndicatorView) mDotContainerView.getChildAt(mCurrentPosition);
      oldIndicatorView.setDrawable(mNormalDrawable);
      //当前位置的圆点变色
      mCurrentPosition = position; //从0-2的32次方
      DotIndicatorView currentIndicatorView= (DotIndicatorView) mDotContainerView.getChildAt(position%mAdapter.getCount());
      currentIndicatorView.setDrawable(mFocusDrawable);

  }


  private boolean showDesc =false;
  public void setshowDesc(boolean showDesc){
      this.showDesc=showDesc;
  }

//    public boolean showDesc(){
//        return showDesc;
//    }


  private boolean showDotIndicator =false;
  public void showDotIndicator(boolean showDotIndicator){
      this.showDotIndicator=showDotIndicator;
  }

//    public boolean showDotIndicator(){
//        return showDotIndicator;
//    }

  /**
   * 根据适配器的count创建圆点并增加至容器
   */
  private void initDotIndicator() {
      int count = mAdapter.getCount();
      mDotContainerView.setGravity(getDotGravity());
      for(int i=0;i<count;i++){
          //不断的往点的指示器添加圆点
          DotIndicatorView dotIndicatorView = new DotIndicatorView(mContext);
          //给圆点设置LayoutParams
          LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mDotSize,mDotSize);
          params.leftMargin=params.rightMargin= mDotDistance;
          dotIndicatorView.setLayoutParams(params);
          //默认选择第一张
          if(i==0){
              dotIndicatorView.setDrawable(mFocusDrawable);
          }else {
              dotIndicatorView.setDrawable(mNormalDrawable);
          }
          //往容器里增加圆点
          Log.d("添加","1");
          mDotContainerView.addView(dotIndicatorView);
      }
  }


  /**
   * 根据mDotGravity值判断Gravity的位置
   * @return
   */
  private int getDotGravity() {
      switch (mDotGravity){
          case 0:
              return Gravity.CENTER;
          case -1:
              return Gravity.RIGHT;
          case 1:
              return Gravity.LEFT;
      }
      return Gravity.CENTER;
  }

  public void startRoll() {
      mBannerViewPager.startRoll();
  }

  /**
   * 把dip转成px
   * @param dip
   * @return
   */
  private int dip2px(int dip) {
      return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
      ,dip,getResources().getDisplayMetrics());
  }


  public void setBannerItemClickListener(BannerItemClickListener clickListener){
      this.mClickListener = clickListener;

  }
  //点击回调监听
  public interface BannerItemClickListener{
      void click(int position);
  }
}

自动播放:

  1. Time实现一个计时器
  2. Handler发送消息
  3. start Thread() 开启一个子线程

改变viewPager的切换速率:

上一篇 下一篇

猜你喜欢

热点阅读