美文网首页
Android无限广播轮播

Android无限广播轮播

作者: Thor_果冻 | 来源:发表于2019-01-03 15:15 被阅读0次

    MyCustomBannerView

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
    dependencies {
        compile 'com.github.Thor-jelly: MyCustomBannerView:最新版本号'
    }
    

    Android无限广播轮播

    使用了类似view复用的方法

    if (reuseView == null) {
        iv = new ImageView(MainActivity.this);
    } else {
        iv = (ImageView) reuseView;
        Log.d(TAG, "getView: 界面复用View"+reuseView);
    }
    

    分析实现方式

    • 系统ViewPager + 自定义 extends ViewPagers
    • 自定义ViewGroup + extends HorizontalScrollView

    参数的传递

    • 传递图片链接数组
    • Adapter适配器模式

    ViewPager源码分析

    参照Android无限广告轮播 - ViewPager源码分析

    实现

    1. 自定义View BannerViewPager

      • 创建自定义View Pager
      /**
       * 类描述:自定义Banner <br/>
       * 创建人:吴冬冬<br/>
       * 创建时间:2018/3/1 14:50 <br/>
       */
      public class BannerViewPager extends ViewPager {
          /**
           * 自定义BannerView,默认的自定义的adapter
           */
          private BannerAdapter mBannerAdapter;
      
          public BannerViewPager(Context context) {
              super(context);
          }
      
          public BannerViewPager(Context context, AttributeSet attrs) {
              super(context, attrs);
          }
      
          public void setAdapter(BannerAdapter adapter) {
              mBannerAdapter = adapter;
              //设置父类ViewPager的adapter
              setAdapter(new BannerPagerAdapter());
          }
      
          /**
           * 给ViewPager设置适配器
           */
          private class BannerPagerAdapter extends PagerAdapter {
              @Override
              public int getCount() {
                  //为了实现无限循环
                  return Integer.MAX_VALUE;
              }
      
              @Override
              public boolean isViewFromObject(View view, Object object) {
                  //官方推荐这么写,源码中写了
                  return view == object;
              }
      
              //创建viewpager 条目回调方法
              @Override
              public Object instantiateItem(ViewGroup container, int position) {
                  /*
                      如果这里写死,比如直接ImageView imageView = new ImageView();
                      所以这里要使用adapter设置模式,为了让我们自定义
                   */
                  View bannerItemView = mBannerAdapter.getView(position);
                  //添加自定义的View样式
                  container.addView(bannerItemView);
                  return bannerItemView;
              }
      
              //销毁ViewPager 条目回调方法
              @Override
              public void destroyItem(ViewGroup container, int position, Object object) {
                  /*
                      下面为什么注释呢,可以直接点进去看一下源码,只是抛出一个异常没有做其他处理
                      throw new UnsupportedOperationException("Required method destroyItem was not overridden");
                   */
                  //super.destroyItem(container, position, object);
                  container.removeView((View) object);
                  object = null;//释放一下内存
              }
          }
      }
      
      • 创建适配器
      /**
       * 类描述:<br/>
       * 创建人:吴冬冬<br/>
       * 创建时间:2018/3/1 15:04 <br/>
       */
      public abstract class BannerAdapter {
          /**
           * 根据位置获取ViewPager的子View
           */
          public abstract View getView(int position);
      }
      
      • 在布局中加入自定义ViewPager,并设置适配器
      final List<String> ss = new ArrayList<>();
          ss.add("http://bbs.everychina.com/data/attachment/forum/201312/04/102637cwnwrywccfxwab22.jpg");
          ss.add("http://img3.100bt.com/upload/ttq/20131121/1385034610130_middle.jpg");
          ss.add("http://pic25.nipic.com/20121123/9830190_172507668164_2.jpg");
          ss.add("http://img5q.duitang.com/uploads/item/201504/24/20150424H4855_LfPvj.jpeg");
          ss.add("http://bcs.91.com/rbpiczy/Wallpaper/2014/11/17/91496cd61ea94d1598345b94c6d246f7-9.jpg");
          mBannerViewPager.setAdapter(new BannerAdapter() {
              @Override
              public View getView(int position) {
                  Log.d(TAG, "getView: "+position);
                  int p = position;
                  if (p >= ss.size()) {
                      p = ss.size() - 1;
                  }
                  ImageView iv = new ImageView(MainActivity.this);
                  Glide.with(MainActivity.this)
                          .load(ss.get(p))
                          .apply(new RequestOptions().placeholder(R.mipmap.ic_launcher))
                          .into(iv);
                  return iv;
              }
          });
      
    2. 实现无限循环效果
      实现自动轮播的方式

      • Timer 写一个定时器
      • Handler发送消息(Handle可能出现内存泄漏问题,activity生命周期没有handle生命周期大)
      • start Thread() 开一个子线程

      我们这里采用Handle的方式:

      private Handler mHandler = new Handler(){
          @Override
          public void handleMessage(Message msg) {
              //每隔多少秒后切换到下一页
              setCurrentItem(getCurrentItem() + 1);
              //不断执行
              startRoll();
          }
      };
      
      /**
       * 实现自动轮播
       */
      public void startRoll() {
          //清除消息,防止多次发送
          mHandler.removeMessages(SCROLL_MSG);
      
          //发送延迟空消息,实现自动轮播
          mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mScrollNextTime);
      }
      
      @Override
      protected void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          //销毁Handle 停止发送,解决内存泄漏
          mHandler.removeMessages(SCROLL_MSG);
          mHandler = null;
      }
      
    3. 改变切换的速率
      在setCurrentItem源码中我们可以看到下面一段代码

      private Scroller mScroller;
      mScroller.startScroll(sx, sy, dx, dy, duration);
      

      而改变速率只有这一个方式,因此我们需要改变duration的时间但是duration是一个局部变量无法改变,因此我们只能改变mScroller(通过反射方法)。

      • 自定义Scroller方法
      /**
       * 类描述:<br/>
       * 创建人:吴冬冬<br/>
       * 创建时间:2018/3/1 17:04 <br/>
       */
      public class BannerScroller extends Scroller {
          /**
           * 动画持续时间
           */
          private int mScrollDuration = 850;
          /**
           * 设置页面切换动画持续时间
           */
          public void setScrollDuration(int scrollDuration) {
              mScrol  lDuration = scrollDuration;
          }
      
          public BannerScroller(Context context) {
              this(context, null);
          }
      
          public BannerScroller(Context context, Interpolator interpolator) {
              this(context, interpolator, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
          }
      
          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, mScrollDuration);
          }
      }
      
      • 添加反射获取到mScroller并重新赋值成我们自定义的Scroller方法

      在构造方法下添加下面代码:

      try {
              //改变ViewPager的速率
              Field mScroller = ViewPager.class.getDeclaredField("mScroller");
              //设置参数
              mScroller.setAccessible(true);
              //第二个参数为插值器,需要的可以添加第二个参数
              mBannerScroller = new BannerScroller(context);
              mScroller.set(this, mBannerScroller);
          } catch (Exception e) {
              e.printStackTrace();
          }
      

      设置持续时间

      /**
       * 设置页面切换动画持续时间
       */
      public void setScrollDuration(int scrollDuration) {
          mBannerScroller.setScrollDuration(scrollDuration);
      }
      
    1. 自定义BannerView

      构建自定义BannerView里面包含:轮播图、当前轮播图描述、轮播图点

      /**
       * 类描述:自定义banner <br/>
       * 创建人:吴冬冬<br/>
       * 创建时间:2018/3/2 10:45 <br/>
       */
      public class BannerView extends RelativeLayout{
      
          /**
           * 轮播图
           */
          private BannerViewPager mBannerVp;
          /**
           * 当前图描述
           */
          private TextView mBannerDescribeTv;
          /**
           * 点布局
           */
          private LinearLayout mBannerDotLl;
      
          /**
           * 自定义的bannerAdapter
           */
          private BannerAdapter mBannerAdapter;
      
          public BannerView(Context context) {
              this(context, null);
          }
      
          public BannerView(Context context, AttributeSet attrs) {
              this(context, attrs, 0);
          }
      
          public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
      
              //加载布局进当前view
              View view = inflate(context, R.layout.banner_layout, this);
              //初始化View
              initView(view);
      
          }
      
          /**
           * 初始化View
           */
          private void initView(View view) {
              mBannerVp = (BannerViewPager) view.findViewById(R.id.banner_vp);
              mBannerDescribeTv = (TextView) view.findViewById(R.id.banner_describe_tv);
              mBannerDotLl = (LinearLayout) view.findViewById(R.id.banner_dot_ll);
          }
      
          /**
           * 设置适配器
           */
          public void setAdapter(BannerAdapter adapter) {
              mBannerAdapter = adapter;
              mBannerVp.setAdapter(adapter);
          }
      
          /**
           * 实现自动轮播
           */
          public void startRoll() {
              mBannerVp.startRoll();
          }
      
          /**
           * 设置页面切换动画持续时间
           */
          public void setScrollDuration(int scrollDuration) {
              mBannerVp.setScrollDuration(scrollDuration);
          }
      }
      
    ```
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <!--滚动条-->
        <com.dongdongwu.test.banner.BannerViewPager
            android:id="@+id/banner_vp"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    
        <!--广告描述和小点布局-->
        <RelativeLayout
            android:paddingBottom="5dp"
            android:paddingTop="5dp"
            android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:background="#66000000"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true">
    
            <!--当前广告的描述-->
            <TextView
                android:id="@+id/banner_describe_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#fff"
                android:text="广告的描述"
                android:textSize="12dp" />
    
            <!--点布局-->
            <LinearLayout
                android:id="@+id/banner_dot_ll"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_centerVertical="true">
    
            </LinearLayout>
        </RelativeLayout>
    
    </RelativeLayout>
    ```
    
    1. 初始化点的指示器和广告位

      /**
       * 设置适配器
       */
      public void setAdapter(BannerAdapter adapter) {
          mBannerAdapter = adapter;
          mBannerVp.setAdapter(adapter);
      
          //初始化点的指示器
          initDotIndicator();
      
          //设置初始化的第一条广告
          mBannerDescribeTv.setText(mBannerAdapter.getBannerDescribe(mCurrentDotPosition));
      
          //设置轮播后广告和小点选中
          mBannerVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
              @Override
              public void onPageSelected(int position) {
                  //监听当前选中的位置,并改变小点状态
                  pageSelect(position % mBannerAdapter.getCount());
              }
          });
      }
      
      /**
       * 监听当前选中的位置,并改变小点状态
       * 设置广告描述
       */
      private void pageSelect(int position) {
          //把之前的点变成未选中状态
          DotIndicatorView oldDotView = (DotIndicatorView) mBannerDotLl.getChildAt(mCurrentDotPosition);
          oldDotView.setDrawable(mDotIndicatorNoSelectDrawable);
      
          //把position位置的点变成选中状态
          mCurrentDotPosition = position;
          DotIndicatorView nowDotView = (DotIndicatorView) mBannerDotLl.getChildAt(mCurrentDotPosition);
          nowDotView.setDrawable(mDotIndicatorSelectDrawable);
      
          //设置广告
          mBannerDescribeTv.setText(mBannerAdapter.getBannerDescribe(mCurrentDotPosition));
      }
      
      /**
       * 初始化点的指示器
       */
      private void initDotIndicator() {
          //获取轮播图数量,也就是要添加点的数量
          int dotCount = mBannerAdapter.getCount();
      
          //让点的位置在右边
          mBannerDotLl.setGravity(Gravity.RIGHT);
      
          for (int i = 0; i < dotCount; i++) {
              //不断往点的指示器中添加点
              DotIndicatorView dotIndicatorView = new DotIndicatorView(mContext);
              LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp2px(8), dp2px(8));
              //设置左右间距
              params.leftMargin = params.rightMargin = dp2px(3);
              //设置大小
              dotIndicatorView.setLayoutParams(params);
              //设置背景
              if (i == 0) {
                  //选中点
                  dotIndicatorView.setDrawable(mDotIndicatorSelectDrawable);
              } else {
                  //未选中点
                  dotIndicatorView.setDrawable(mDotIndicatorNoSelectDrawable);
              }
              //把点添加进指示器中
              mBannerDotLl.addView(dotIndicatorView);
          }
      }
      
    2. 自定义属性

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
          <declare-styleable name="BannerView">
              <!--点选中的颜色-->
              <attr name="dotIndicatorSelectColor" format="color|reference"/>
              <!--点未选中的颜色-->
              <attr name="dotIndicatorNoSelectColor" 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="right" value="1"/>
                  <enum name="left" value="-1"/>
              </attr>
              <!--底部条颜色-->
              <attr name="bottomColor" format="color"/>
              <!--宽高比-->
              <attr name="wideProportion" format="float" />
              <attr name="heightProportion" format="float" />
          </declare-styleable>
      </resources>
      
    3. 初始化自定义属性

      /**
       * 初始化自定义属性
       */
      private void initAttribute(AttributeSet attrs) {
          TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
      
          //获取点的位置
          mDotGravity = array.getInt(R.styleable.BannerView_dotGravity, 1);
          //获取小点选中和未选中样式
          mDotIndicatorSelectDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorSelectColor);
          if (mDotIndicatorSelectDrawable == null) {
              mDotIndicatorSelectDrawable = new ColorDrawable(0xffff0000);
          }
          mDotIndicatorNoSelectDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorNoSelectColor);
          if (mDotIndicatorNoSelectDrawable == null) {
              mDotIndicatorNoSelectDrawable = new ColorDrawable(0xffffffff);
          }
          //点的大小
          mDotSize = (int) array.getDimension(R.styleable.BannerView_dotSize, 8);
          //点的间距
          mDotDistance = (int) array.getDimension(R.styleable.BannerView_dotDistance, 4);
          //底部条颜色
          mBottomColor = array.getColor(R.styleable.BannerView_bottomColor, 0x00000000);
          //宽高比
          mWideProportion = array.getFloat(R.styleable.BannerView_wideProportion, 0);
          mHeightProportion = array.getFloat(R.styleable.BannerView_heightProportion, 0);
      
          //最后array需要回收
          array.recycle();
      }
      

    参考

    Android无限广告轮播 - 自定义BannerView

    相关文章

      网友评论

          本文标题:Android无限广播轮播

          本文链接:https://www.haomeiwen.com/subject/likplqtx.html