循环广告位组件的实现



技术细节

实现这样一个广告位组件,其技术细节主要有3点:
1. 如何使用ViewPager
2. 如何实现循环播放
3. 如何实现自动播放
下面分别介绍这三点。

如何使用ViewPager

ViewPager是support-v4提供的一个类,它主要用于实现滑屏的效果,它和Fragment是一对完美的组合,通过FragmentPagerAdapter,ViewPager能够轻松地管理多个Fragment。对于本例来说,由于不需要使用Fragment,所以我们要用到ViewPager的另一个更加通用的Adapter:PagerAdapter。PagerAdapter是FragmentPagerAdapter的父类,其提供了更加一般的功能接口,当然这也意味着PagerAdapter使用起来稍微复杂些。

一般来说,使用PagerAdapter最少需要实现如下四个方法:
1. int getCount()
表示ViewPager的屏幕个数
2. boolean isViewFromObject(View view, Object o)
表示key:o和value:view是否属于同一个键值对,即o对应的值是否是view
3. Object instantiateItem(ViewGroup container, int position)
初始化位置为position的屏幕的界面,返回值为键值对的key,而真正的View是key所对应的value。这个概念有点抽象,一般情况下我们采用View作为key,即key和value都是同一个View对象,在这个方法中,我们需要将View加载出来并作为返回值返回。考虑到ViewPager和AdapterView的使用场景的差异,本文中并没有采用View的复用机制,这是因为ViewPager不像AdapterView那样具有大量的内容,而且ViewPager的PagerAdapter在设计时就没有暴露出复用的接口,同时广告位组件中一般都是由图片组成,布局比较简单,因此每次都重新加载布局所带来的开销是可以接受的。
4. void destroyItem(ViewGroup container, int position, Object object)
当位置为position的屏幕不再使用时,销毁它,典型的行为是将此屏幕的View对象从container中remove掉。

对于本文的例子来说,对这4个方法的实现如下所示:

<code class="hljs java has-numbering">        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getCount</span>() {
            <span class="hljs-keyword">return</span> FAKE_BANNER_SIZE;
        }

        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isViewFromObject</span>(View view, Object o) {
            <span class="hljs-keyword">return</span> view == o;
        }

        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> Object <span class="hljs-title">instantiateItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position) {
            position %= DEFAULT_BANNER_SIZE;
            View view = mInflater.inflate(R.layout.item, container, <span class="hljs-keyword">false</span>);
            ImageView imageView = (ImageView) view.findViewById(R.id.image);
            imageView.setImageResource(mImagesSrc[position]);
            <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> pos = position;
            view.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
                <span class="hljs-annotation">@Override</span>
                <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span>(View v) {
                    Toast.makeText(MainActivity.<span class="hljs-keyword">this</span>, <span class="hljs-string">"click banner item :"</span> + pos, Toast.LENGTH_SHORT).show();
                }
            });
            container.addView(view);
            <span class="hljs-keyword">return</span> view;
        }

        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">destroyItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position, Object object) {
            container.removeView((View) object);
        }</code>

如何使用循环播放

循环播放这个问题,如果从常规思想去考虑就会变得很复杂,但是换一种思路就会豁然开朗,考虑一种情况:假设目前我们需要ViewPager有5屏,但是如果在PagerAdapter的getCount方法中我们返回100,即告诉ViewPager我们有100屏,那么会发生什么呢?试想一下,如果当前位于第5屏,由于对于ViewPager来说它认为有100屏,所以我们仍然可以向后滑动,当滑动到第6屏时,如果我们在instantiateItem中将第1屏的View返回给它会怎么样?很好理解,这就实现了从第5屏滑动到第1屏的效果了,即从最后一屏循环滑动到了第一屏。同理,当第一屏向前滑动时,我们同样可以返回最后一屏的View,但是这里有一个问题,第一屏没办法向前滑动,因为第一屏的位置是0,ViewPager不能滑动到-1的位置,这的确是个问题!但是仍然难不倒我们,试想一下:如果ViewPager滑动到0时,这个时候我们迅速将其切换到第6屏会怎么样?由于第6屏的内容实际上就是第1屏的内容,因此界面上感受不到任何变化,但是这个时候ViewPager当前屏幕的下标已经从0变为5了,这意味着什么?这意味着ViewPager可以向前滑动了。

这样一来,循环播放的问题就解决了,对于本例来说,代码实现如下:

<code class="hljs java has-numbering">        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getCount</span>() {
            <span class="hljs-keyword">return</span> FAKE_BANNER_SIZE;
        }

        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> Object <span class="hljs-title">instantiateItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position) {
            position %= DEFAULT_BANNER_SIZE;
            View view = mInflater.inflate(R.layout.item, container, <span class="hljs-keyword">false</span>);
            ImageView imageView = (ImageView) view.findViewById(R.id.image);
            imageView.setImageResource(mImagesSrc[position]);
            <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> pos = position;
            view.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
                <span class="hljs-annotation">@Override</span>
                <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span>(View v) {
                    Toast.makeText(MainActivity.<span class="hljs-keyword">this</span>, <span class="hljs-string">"click banner item :"</span> + pos, Toast.LENGTH_SHORT).show();
                }
            });
            container.addView(view);
            <span class="hljs-keyword">return</span> view;
        }</code>


从上面的代码可以看出,getCount返回了一个常量FAKE_BANNER_SIZE,这个常量被我定义为100,当然50、200和1000等等都可以,但是不能太大,比如Integer.MAX_VALUE,太大的数会以极大的概率导致程序ANR。然后在instantiateItem方法中如下一句话非常关键:

position %= DEFAULT_BANNER_SIZE

这句话完成了100到5的映射,即将ViewPager的屏幕下标映射为实际所需的下标,其中DEFAULT_BANNER_SIZE被定义为5,表示ViewPager真正的屏幕数量。

除此之外,为了实现从第一屏向前滑动到最后一屏的效果,还需要做如下处理:

<code class="hljs java has-numbering">        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">finishUpdate</span>(ViewGroup container) {
            <span class="hljs-keyword">int</span> position = mBanner.getCurrentItem();
            Log.d(TAG, <span class="hljs-string">"finish update before, position="</span> + position);
            <span class="hljs-keyword">if</span> (position == <span class="hljs-number">0</span>) {
                position = DEFAULT_BANNER_SIZE;
                mBanner.setCurrentItem(position, <span class="hljs-keyword">false</span>);
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (position == FAKE_BANNER_SIZE - <span class="hljs-number">1</span>) {
                position = DEFAULT_BANNER_SIZE - <span class="hljs-number">1</span>;
                mBanner.setCurrentItem(position, <span class="hljs-keyword">false</span>);
            }
            Log.d(TAG, <span class="hljs-string">"finish update after, position="</span> + position);
        }</code><ul class="pre-numbering" style="display: block;"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li></ul>

finishUpdate表示ViewPager的更新即将完成,因此在这个时候我们可以悄悄地替换一些东西,比如将屏幕0切换为屏幕5,同时为了实现无限地循环滑动,当下标达到99时,将其切换到第4屏,这样滑动就可以无限地进行下去,否则ViewPager达到第100屏时将无法再继续向后滑动。

如何实现自动播放

这个问题就稍微简单些了,比如采用Timer、Alarm甚至Handler都可以实现,思想就是以一定的时间间隔触发ViewPager的滑动即可,本文中采用轻量级的Timer来实现,理由如下:Alarm太重量级,有点大材小用的感觉,而Handler使用起来稍微复杂一点点。下面是具体的实现细节,每隔3000ms就触发一次自动播放。

<code class="hljs java has-numbering">    <span class="hljs-keyword">private</span> TimerTask mTimerTask = <span class="hljs-keyword">new</span> TimerTask() {
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {
            <span class="hljs-keyword">if</span> (!mIsUserTouched) {
                mBannerPosition++;
                runOnUiThread(MainActivity.<span class="hljs-keyword">this</span>);
                Log.d(TAG, <span class="hljs-string">"tname:"</span> + Thread.currentThread().getName());
            }
        }
    };

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        mTimer.schedule(mTimerTask, <span class="hljs-number">5000</span>, <span class="hljs-number">3000</span>);
    }

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {
        mBanner.setCurrentItem(mBannerPosition);
    }</code>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值