打造一个简单实用的安卓广告栏控件

思路

循环 ViewPager 的两种实现方法这篇文章中介绍了广告栏的两种实现思路,但是直接用到项目中还是会有不少问题。

  • 方法1:将 count 设为无限大,制造一种假的循环
    这种方法在实际的项目中容易导致anr,在调用setCurrentItem或者在数据集发生改变时调用notifyDataSetChanged时有发生。
  • 方法2:在 ViewPager 的首尾添加一个重复的 View
    这种做法的问题是每循环一次会额外的多调用一次setCurrentItem,性能不佳,尤其是用户快速滚动时表现不够流畅。

能否将两种方法结合起来呢,比如我将count设为200个,每次滑动到最后一页或者第一页的时候再执行setCurrentItem(middleItem)。当然,我还需要对滑出去的View做好回收,这点仿照ListView去做即可。

说干就干。

实现我们的PagerAdapter

看码说话

public abstract class CyclePagerAdapter extends PagerAdapter {

    private final int MAX_PAGES = 200;
    // 对View做缓存,防止每次都去inflate
    protected LinkedList<View> mScrapViews = new LinkedList<View>();
    // 最多缓存两个View
    protected int mMaxScrapViewSize = 2;

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View scrap = retrieveFromScrap();
        View view = getView(position, scrap, container);
        container.addView(view);
        return view;
    }

    private View retrieveFromScrap() {
        if (mScrapViews.size() > 0) {
            return mScrapViews.removeLast();
        }
        return null;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        View view = (View) object;
        container.removeView(view);
        if (mScrapViews.size() < mMaxScrapViewSize) {
            mScrapViews.add(view);
        }
    }
    // 返回 getRealCount 的整数倍,该数最大值为 MAX_PAGES,这里将MAX_PAGES设为200。
    @Override
    public int getCount() {
        if (getRealCount() < 2) {
            return getRealCount();
        }
        return getRealCount() * (MAX_PAGES / getRealCount());
    }

    public View getView(int position, View convertView, ViewGroup container) {
        int realPosition = getRealPosition(position);
        return getViewAtRealPosition(realPosition, convertView, container);
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    public int getRealPosition(int position) {
        if (getRealCount() == 0) {
            return 0;
        }
        return position % getRealCount();
    }

    public abstract int getRealCount();

    public abstract View getViewAtRealPosition(int position, View convertView, ViewGroup container);

}

在CyclePagerAdapter中,getCount返回值最大为200,并且该数是getRealCount的整数倍。这里我们还添加了一个回收机制,防止多次创建View导致性能损耗。

使用时只需要继承CyclePagerAdapter即可。

public class SimpleBannerAdapter extends CyclePagerAdapter {

        private static final int[] drawableIds = new int[]{R.drawable.desert, R.drawable.koala,
                R.drawable.jellyfish, R.drawable.hydrangeas};
        private Context mContext;

        public SimpleBannerAdapter(Context context) {
            this.mContext = context;
        }

        @Override
        public int getRealCount() {
            return drawableIds.length;
        }

        @Override
        public View getViewAtRealPosition(final int position, View convertView, ViewGroup container) {
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.banner_item, container, false);
            }
            ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView);
            imageView.setImageResource(drawableIds[position]);
            return convertView;
        }
    }

监听ViewPager滚动

public class CycleViewPager extends ViewPager {
    private CyclePagerAdapter mCyclePagerAdapter;
    @Override
    public void setAdapter(PagerAdapter adapter) {
        super.setAdapter(adapter);
        if (adapter instanceof CyclePagerAdapter) {
            mCyclePagerAdapter = (CyclePagerAdapter) adapter;
            addOnPageChangeListener(mOnPageChangeListener);
            setMiddleItemInner(false, true);
        }
    }
    private OnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float offset, int offsetPixels) {
            if (offset != 0) {
                return;
            }
            if (mCyclePagerAdapter == null || mCyclePagerAdapter.getRealCount() <= 1) {
                return;
            }
            // 第一页
            if (position == 0) {
                setMiddleItemInner(false, false);
                //最后一页
            } else if (position == mCyclePagerAdapter.getCount() - 1) {
                setMiddleItemInner(false, false);
            }
        }

        @Override
        public void onPageSelected(int position) {
           
        }

    };
    // 设置到中间的item。当ViewPager滚动到第一页或者最后一页的时候调用。
    public void setMiddleItem() {
        setMiddleItemInner(true, true);
    }

    private void setMiddleItemInner(boolean setToFirstItem, boolean immediately) {
        if (mCyclePagerAdapter != null && mCyclePagerAdapter.getRealCount() > 1) {
            int currentItem = setToFirstItem ? 0 : getCurrentItem();
            final int middleItem = mCyclePagerAdapter.getCount() / 2 + mCyclePagerAdapter.getRealPosition(currentItem);
            if (immediately) {
                setCurrentItem(middleItem, false);
            } else {
                post(new Runnable() {
                    @Override
                    public void run() {
                        setCurrentItem(middleItem, false);
                    }
                });
            }
        }
    }
}

至此,我们已经实现了一个可以循环滚动的ViewPager了,当然,自动滚动以及ViewPager指示器我们都还没有实现,如果想了解这部分可以参考我的github。我已经将这个项目上传到 https://github.com/leandom/CycleViewPager 这里了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,481评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 第一,抵御诱惑 诱惑来自生活中的各种打扰,来自网络、手机和各种外在影响,来自内心抵御这些干扰带来的心力损耗。 首先...
    withinsky阅读 208评论 0 0
  • 你相信爱情吗?无论你是否受过伤,你都一直坚持地相信下去。有答案吗?我不知道,其实答案只是我们内心对自己一段岁月的结...
    噗梅阅读 109评论 0 0
  • 当洛风回望自己多年以前的自己,记忆便一下子泄开了口子,带着不可阻挡的势,席卷了已经注释好整个人生的缀角。 该怎么描...
    松风山月阅读 597评论 0 1