ViewPager打造真正意义的无限轮播

发布于 2021-3-1 17:13
浏览
0收藏

 

文章目录


1 简述
2 实现思路
3 具体实现
3.1 实现无限滚动
3.2 添加指示圆点
3.3 自动轮播
3.4 触摸停止和点击跳转
4 总结
5 参考
 

 

1 简述


ViewPage 不仅常用于页面导航切换,也常用来实现轮播图。百度一下,可以找到很多关于轮播图的实现文章。曾翻看过多篇相关文章,get 到一些要点,然而觉得自己实现一下,会更加深刻,如果加上自己独特的思路,也是对自己的一个锻炼,对代码一个积累。实现目标如下图所示:

ViewPager打造真正意义的无限轮播-开源基础软件社区

2 实现思路


ViewPager 实现轮播图早已为我们所熟悉,我个人认为,实现方式主要有2种:

让 PagerAdapter 的 getCount() 返回 Integer.MAX_VALUE ,这个非常大的数字使轮播几乎没有尽头,通过 currentItem 对数据源的大小(size)求余来获取当前页面数据。是一个简单易懂的实现方式,但不是真正意义的无限轮播。而且,在页面数量为 2 时,只能左右滚动,失去无限轮播效果(解决:可以再添加一遍数据源,达到4个页面)。
通过不断改变 PagerAdapter 的数据源,结合 ViewPager 的刷新,真正实现无限轮播。实现难度稍大。在页面数量为 2 时,也需要再添加一遍数据源。

ViewFlipper 也可以实现轮播。


本篇就用第二种方式实现真正意义的无限轮播。

 

3 具体实现


3.1 实现无限滚动


第1步: 布局
layout/activity_banner.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".banner.BannerActivity">

    <FrameLayout
        android:id="@+id/fl_banner_frame"
        android:layout_width="match_parent"
        android:layout_height="180dp">

        <android.support.v4.view.ViewPager
            android:id="@+id/vp_banner"
            android:layout_width="match_parent"
            android:layout_height="180dp" />

        <LinearLayout
            android:id="@+id/ll_banner_dot_group"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="6dp"
            android:orientation="horizontal">

        </LinearLayout>

    </FrameLayout>

</LinearLayout>

 

layout/layout_banner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="180dp">

    <ImageView
        android:id="@+id/iv_banner_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@color/common_color"
        />

    <TextView
        android:id="@+id/tv_banner_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#1b606060"
        android:textColor="@color/color_white"
        android:text="为什么说 5G 是物联网的时代?"
        android:lines="2"
        android:layout_gravity="bottom"
        android:padding="12dp"
        />

</FrameLayout>

 

第2步: 准备数据

 

实体类:

public class BannerEntity implements Cloneable {
    private int id;
    private String imgUrl;
    private String desc;

    public BannerEntity() {
    }

    public BannerEntity(int id, String imgUrl, String desc) {
        this.id = id;
        this.imgUrl = imgUrl;
        this.desc = desc;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    protected BannerEntity clone() {
        return new BannerEntity(getId(), getImgUrl(), getDesc());
    }
}

 

初始化数据:

/**
 * 初始化数据时,如果数据量大于2, `mBannerList.size() >  2` ,无需特殊处理;
 * 如果等于2,因为 ViewPager 有离屏缓存,需要将两条数据复制一份添加到 mBannerList 中;
 * 如果等于1,不需要滚动,不作无限轮播处理。
 */
private void initData() {
    mBannerList = new ArrayList<BannerEntity>();
    int id = 0;
    mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/cb8be21b1f7ce2256ffd6c7b8b737a74.png", "为什么说 5G 是物联网的时代?"));
    mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/075ff654f74cf80660112b03e48c2896.jpg", "新技术“红”不过十年,半监督学习为什么是个例外?"));
    mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/354fc1a74a651de1e0291b4e9261d77c.jpg", "阿里达摩院SIGIR 2019:AI判案1秒钟,人工2小时"));
    mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190522/0e36975c84e6e3fb0e576556a1168330.png", "独家!天才少年 Vitalik:“中国开发者应多关注以太坊!”"));
    mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/8aee33b2a4ef11b0fb70bf371484c2ee.jpg", "不是码农,不会敲代码的她,却最懂程序员!| 人物志"));

    //记录原始数据大小,表示指示圆点数量。在下面特殊情况处理之前。
    mOriSize = mBannerList.size();

    //处理等于 2 的情况。
    if (mBannerList.size() == 2) {
        BannerEntity clone0 = mBannerList.get(0).clone();
        BannerEntity clone1 = mBannerList.get(1).clone();
        mBannerList.add(clone0);
        mBannerList.add(clone1);
    }
}

 

初始化数据时,如果数据量大于2, mBannerList.size() > 2 ,无需特殊处理;如果等于2,因为 ViewPager 有离屏缓存,需要将2条数据再次添加一遍到 mBannerList 中(id 分别为:0, 1, 0, 1),这里用到了克隆;如果等于1,不需要滚动,不作无限轮播处理。

 

如果要显示第一页,需要从第 1 页开始滚动,如下代码

 

@Override
public void initView() {
    mFlBannerFrame = (FrameLayout) findViewById(R.id.fl_banner_frame);
    mLlBannerDotGroup = (LinearLayout) findViewById(R.id.ll_banner_dot_group);
    mVpBanner = ((ViewPager) findViewById(R.id.vp_banner));
    mPagerAdapter = new BannerPagerAdapter();
    mVpBanner.setAdapter(mPagerAdapter);

    initBanner();
}

/**
 * 初始化轮播图。
 * 
 */
private void initBanner() {
    mFlBannerFrame.setVisibility(View.VISIBLE);
    mLlBannerDotGroup.setVisibility(View.VISIBLE);
    if (mBannerList.size() > 1) {
        //根据原始数据大小创建指示圆点
        for (int i = 0; i < mOriSize; i++) {
            CheckedTextView rbDot = (CheckedTextView) getLayoutInflater().inflate(R.layout.view_banner_dot_round, mLlBannerDotGroup, false);
            mLlBannerDotGroup.addView(rbDot);
            if (i == 0) {
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rbDot.getLayoutParams();
                lp.leftMargin = 0;
            }
        }
        //设置当前页为 1 下标页
        mVpBanner.setCurrentItem(1);
        ((CheckedTextView) mLlBannerDotGroup.getChildAt(0)).setChecked(true);
    } else if (mBannerList.size() == 1) {
        mVpBanner.setCurrentItem(0);
        mLlBannerDotGroup.setVisibility(View.GONE);
    } else {
        mFlBannerFrame.setVisibility(View.GONE);
    }
}

 

如下图所示,我们不用原始数据 mBannerList 直接作为 PagerAdapter 的数据源,而是用一个集合 mDataList 作为数据源,每次切换页面时,由于 ViewPager 显示当前页面的同时会默认缓存左右两个页面,其实理论上,我们只需要从原始数据 mBannerList 中取出当前页面及左右两个页面对应的数据即可。如果要显示第一页,当前页面理论下标为 0,三条数据在mBannerList 中的位置应该为 [4, 0, 1],如果当前页面要显示第二页,页面理论下标为 1, 三条数据在 mBannerList 中的位置为 [0, 1, 2],…,如果当前页面要显示第五页,页面理论下标为 4,三条数据在 mBannerList 中的位置为 [3, 4, 0]。

ViewPager打造真正意义的无限轮播-开源基础软件社区

我们在每次页面切换后,清空 PagerAdapter 数据 mDataList ,从 mBannerList 中取出对应的三条数据添加到 mDataList,再刷新一下界面,理论上就可以实现无限轮播效果了。

 

第3步: 实现适配器

 

    class BannerPagerAdapter extends PagerAdapter {

        private ArrayList<BannerEntity> mDataList = new ArrayList<>();

        public BannerPagerAdapter() {
            resetData(0);
        }

        public void resetData(int position) {
            if (mBannerList.size() > 1) {
                int leftPos = (mBannerList.size() + position - 1) % mBannerList.size();
                int rightPos = (position + 1) % mBannerList.size();
                mDataList.clear();
                mDataList.add(mBannerList.get(leftPos));
                mDataList.add(mBannerList.get(position));
                mDataList.add(mBannerList.get(rightPos));
            } else if (mBannerList.size() == 1){
                mDataList.add(mBannerList.get(position));
            }

        }

        public BannerEntity getItem(int position) {
            return mDataList.get(position);
        }

        @Override
        public int getCount() {
            return mDataList.size();
        }

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

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            BannerEntity bannerEntity = mDataList.get(position);
            View view = LayoutInflater.from(container.getContext()).inflate(R.layout.layout_banner_item, container, false);
            ImageView ivBannerImg = view.findViewById(R.id.iv_banner_img);
            TextView tvBannerDesc = view.findViewById(R.id.tv_banner_desc);
            Glide.with(BannerActivity.this).load(bannerEntity.getImgUrl()).centerCrop().into(ivBannerImg);
            tvBannerDesc.setText(bannerEntity.getDesc());
            view.setTag(bannerEntity);
            container.addView(view);
            return bannerEntity;
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            container.removeView(container.findViewWithTag(object));
        }

        public void updateData() {
            if (mDataList.size() > 1) {
                //传入 mBannerList 中的位置
                resetData(mBannerList.indexOf(mDataList.get(mVpBanner.getCurrentItem())));
                notifyDataSetChanged();
            }
        }

        @Override
        public int getItemPosition(@NonNull Object object) {
            if (mDataList.contains(object)) {
                return mDataList.indexOf(object);
            } else {
                return POSITION_NONE;
            }
        }
    }

代码中,BannerPagerAdapter 继承 PagerAdapter。


重写了通常的4个方法,内部处理有所不同:

  • getCount()
    返回 mDataList.size()。
  • instantiateItem(ViewGroup, int)
    返回值不是通常的 View ,而是 BannerEntity 对象;而且将 BannerEntity 对象作为 tag 缓存在每个页面的 View 中(view.setTag(bannerEntity);)。
  • isViewFromObject(View, Object)
    需要通过缓存的tag值做判断。
  • destroyItem(ViewGroup, int, Object)
    需要通过对象找到对应的View,再移除不需要的View。
     
    还重写了 1 个不常用的方法:
  • getItemPosition(Object)
    当 adapter 的数据有变化时(增、删、改、换位),需要重写此方法,adapter 的 notifyDataSetChanged() 方法才起作用。一般情况下,我们的ViewPager页面是固定的,PagerAdapter 源码中此方法返回默认值 POSITION_UNCHANGED,调用刷新方法页面不改变。
    如果 adapter 数据有变化,需重写 getItemPosition(Object) ,

根据情况返回以下三种值:

  • POSITION_UNCHANGED(默认值:没有变化)
  • POSITION_NONE(位置不存在,可能已删除)
  • [0, {@link #getCount()}) 范围内(当前新位置)

 

第4步:重置数据源和刷新

 

public void resetData(int position) {
    if (mBannerList.size() > 1) {
        int leftPos = (mBannerList.size() + position - 1) % mBannerList.size();
        int rightPos = (position + 1) % mBannerList.size();
        mDataList.clear();
        mDataList.add(mBannerList.get(leftPos));
        mDataList.add(mBannerList.get(position));
        mDataList.add(mBannerList.get(rightPos));
    } else if (mBannerList.size() == 1){
        mDataList.add(mBannerList.get(position));
    }
}

ViewPager打造真正意义的无限轮播-开源基础软件社区重置数据时,要清空 PagerAdapter 数据 mDataList ,从 mBannerList 中取出对应的三条数据添加到 mDataList。

 

为 ViewPager 设置 页面变化监听,更新数据并刷新:

    @Override
    public void setListeners() {
        mVpBanner.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            private int mCheckedId = 0;

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);

                ((CheckedTextView) mLlBannerDotGroup.getChildAt(mCheckedId)).setChecked(false);
                //设置圆点高亮
                int id = mPagerAdapter.getItem(position).getId();
                ((CheckedTextView) mLlBannerDotGroup.getChildAt(id)).setChecked(true);

                mCheckedId = id;
            }

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (positionOffset == 0) {
                    mPagerAdapter.updateData();
                }
            }
        });
    }

 

自此,就实现了ViewPager的无限滚动效果。

ViewPager打造真正意义的无限轮播-开源基础软件社区

 

 

3.2 添加指示圆点


数据源数据量大于 2 时,圆点数量是数据源大小;等于 2 时, 记录圆点数量为 2, 数据源数据再作特殊处理。见 initData() 方法中代码:

 

    /**
     * 初始化数据时,如果数据量大于2, `mBannerList.size() >  2` ,无需特殊处理;
     * 如果等于2,因为 ViewPager 有离屏缓存,需要将两条数据复制一份添加到 mBannerList 中;
     * 如果等于1,不需要滚动,不作无限轮播处理。
     */
    private void initData() {
        mBannerList = new ArrayList<BannerEntity>();
        int id = 0;
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/cb8be21b1f7ce2256ffd6c7b8b737a74.png", "为什么说 5G 是物联网的时代?"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/075ff654f74cf80660112b03e48c2896.jpg", "新技术“红”不过十年,半监督学习为什么是个例外?"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/354fc1a74a651de1e0291b4e9261d77c.jpg", "阿里达摩院SIGIR 2019:AI判案1秒钟,人工2小时"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190522/0e36975c84e6e3fb0e576556a1168330.png", "独家!天才少年 Vitalik:“中国开发者应多关注以太坊!”"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/8aee33b2a4ef11b0fb70bf371484c2ee.jpg", "不是码农,不会敲代码的她,却最懂程序员!| 人物志"));

        //记录原始数据大小,表示指示圆点数量。在下面特殊情况处理之前。
        mOriSize = mBannerList.size();

        //处理等于 2 的情况。
        if (mBannerList.size() == 2) {
            BannerEntity clone0 = mBannerList.get(0).clone();
            BannerEntity clone1 = mBannerList.get(1).clone();
            mBannerList.add(clone0);
            mBannerList.add(clone1);
        }
    }

 

在 initBanner中初始化圆点,并设置第一个圆点高亮:

 

    /**
     * 初始化轮播图。
     *
     */
    private void initBanner() {
        mFlBannerFrame.setVisibility(View.VISIBLE);
        mLlBannerDotGroup.setVisibility(View.VISIBLE);
        if (mBannerList.size() > 1) {
            //根据原始数据大小创建指示圆点
            for (int i = 0; i < mOriSize; i++) {
                CheckedTextView rbDot = (CheckedTextView) getLayoutInflater().inflate(R.layout.view_banner_dot_round, mLlBannerDotGroup, false);
                mLlBannerDotGroup.addView(rbDot);
                if (i == 0) {
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rbDot.getLayoutParams();
                    lp.leftMargin = 0;
                }
            }
            //设置当前页为 1 下标页
            mVpBanner.setCurrentItem(1);
            //第一个圆点高亮
            ((CheckedTextView) mLlBannerDotGroup.getChildAt(0)).setChecked(true);
        } else if (mBannerList.size() == 1) {
            mVpBanner.setCurrentItem(0);
            mLlBannerDotGroup.setVisibility(View.GONE);
        } else {
            mFlBannerFrame.setVisibility(View.GONE);
        }
    }

 

在 ViewPager 页面改变监听回调方法 onPageSelected 中,切换高亮圆点。

 

@Override
public void onPageSelected(int position) {
    super.onPageSelected(position);
    ((CheckedTextView) mLlBannerDotGroup.getChildAt(mCheckedId)).setChecked(false);
    //设置圆点高亮
    int id = mPagerAdapter.getItem(position).getId();
    ((CheckedTextView) mLlBannerDotGroup.getChildAt(id)).setChecked(true);
    mCheckedId = id;
}

 

用到了一个 CheckedTextView 作为圆点控件,它可以设置 selector 背景。
layout/view_banner_dot_round.xml

 

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="8dp"
    android:layout_height="8dp"
    android:layout_marginLeft="10dp"
    android:background="@drawable/selector_banner_dot_round" />

 

3.3 自动轮播


在 initBanner() 方法中调用 startAutoScroll(),并在 Activity 的 OnDestroy() 中调用 stopAutoScroll() 停止轮播,防止泄漏。

	//......
	
    private class MHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == WHAT_AUTO_SCROLL) {
                int currentItem = mVpBanner.getCurrentItem();
                mVpBanner.setCurrentItem(currentItem + 1);
                mHandler.sendEmptyMessageDelayed(WHAT_AUTO_SCROLL, 2000);
            }
        }
    }
    
    //......
    
    /**
     * 初始化轮播图。
     */
    private void initBanner() {
        mFlBannerFrame.setVisibility(View.VISIBLE);
        mLlBannerDotGroup.setVisibility(View.VISIBLE);
        if (mBannerList.size() > 1) {
            //根据原始数据大小创建指示圆点
            for (int i = 0; i < mOriSize; i++) {
                CheckedTextView rbDot = (CheckedTextView) getLayoutInflater().inflate(R.layout.view_banner_dot_round, mLlBannerDotGroup, false);
                mLlBannerDotGroup.addView(rbDot);
                if (i == 0) {
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rbDot.getLayoutParams();
                    lp.leftMargin = 0;
                }
            }
            //设置当前页为 1 下标页
            mVpBanner.setCurrentItem(1);
            //第一个圆点高亮
            ((CheckedTextView) mLlBannerDotGroup.getChildAt(0)).setChecked(true);

            //开始轮播
            startAutoScroll();
        } else if (mBannerList.size() == 1) {
            mVpBanner.setCurrentItem(0);
            mLlBannerDotGroup.setVisibility(View.GONE);
        } else {
            mFlBannerFrame.setVisibility(View.GONE);
        }
    }

	//......

    private void startAutoScroll() {
        mHandler.sendEmptyMessageDelayed(WHAT_AUTO_SCROLL, 2000);
    }

    private void stopAutoScroll() {
        mHandler.removeMessages(WHAT_AUTO_SCROLL);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopAutoScroll();//在页面销毁时停止轮播,防止泄漏。
    }

 

3.4 触摸停止和点击跳转


为 mVpBanner 设置触摸监听,在手指按下 ACTION_DOWN 时,记录 x 方向位置,调用 stopAutoScroll() 停止轮播;在手指移动 ACTION_MOVE 时,记录手指 x 方向累计移动距离;在手指抬起 ACTION_UP 时,判断累计移动距离是否小于 20,按下抬起时间间隔是否小于800 毫秒,如果累计距离小于20,时间间隔小于 800 毫秒,认定为点击事件,做点击处理逻辑,之后启动轮播,重置累计移动距离为 0 。ACTION_CANCEL 中启动轮播,重置累计移动距离为 0。

		//......
        mVpBanner.setOnTouchListener(new View.OnTouchListener() {

            float currX = 0;
            float dx = 0;
            long timeMillis = 0;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        currX = event.getX();
                        timeMillis = System.currentTimeMillis();
                        stopAutoScroll();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float x = event.getX();
                        dx += Math.abs(x - currX);
                        currX = x;
                        break;
                    case MotionEvent.ACTION_UP:
                        if (dx < 20 && (System.currentTimeMillis() - timeMillis) < 800) {
                            int currentItem = mVpBanner.getCurrentItem();
                            BannerEntity item = mPagerAdapter.getItem(currentItem);
                            Toast.makeText(BannerActivity.this, item.getDesc(), Toast.LENGTH_SHORT).show();
                        }
                        startAutoScroll();
                        dx = 0;
                        break;
                    case MotionEvent.ACTION_CANCEL:
                        dx = 0;
                        startAutoScroll();
                        break;
                }
                return false;
            }
        });
        
	//......

 

 

4 总结


虽然轮播图比较常见,但它涉及的要点并不少,要想尽善尽美,也并不是那么容易。涉及到的 PagerAdapter 的刷新问题,触摸事件处理是不太好整的。

完整 BannerActivity 代码如下:

 

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.wzhy.viewpagerserial.R;
import com.wzhy.viewpagerserial.base.BaseActivity;

import java.util.ArrayList;

public class BannerActivity extends BaseActivity {

    private ViewPager mVpBanner;
    private ArrayList<BannerEntity> mBannerList;
    private BannerPagerAdapter mPagerAdapter;
    private int mOriSize;
    private LinearLayout mLlBannerDotGroup;
    private FrameLayout mFlBannerFrame;
    private MHandler mHandler;
    private static final int WHAT_AUTO_SCROLL = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_banner);
        initData();
        initView();
        setListeners();

    }


    private class MHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == WHAT_AUTO_SCROLL) {
                int currentItem = mVpBanner.getCurrentItem();
                mVpBanner.setCurrentItem(currentItem + 1);
                startAutoScroll();
            }
        }
    }


    /**
     * 初始化数据时,如果数据量大于2, `mBannerList.size() >  2` ,无需特殊处理;
     * 如果等于2,因为 ViewPager 有离屏缓存,需要将两条数据复制一份添加到 mBannerList 中;
     * 如果等于1,不需要滚动,不作无限轮播处理。
     */
    private void initData() {
        mBannerList = new ArrayList<BannerEntity>();
        int id = 0;
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/cb8be21b1f7ce2256ffd6c7b8b737a74.png", "为什么说 5G 是物联网的时代?"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190617/075ff654f74cf80660112b03e48c2896.jpg", "新技术“红”不过十年,半监督学习为什么是个例外?"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/354fc1a74a651de1e0291b4e9261d77c.jpg", "阿里达摩院SIGIR 2019:AI判案1秒钟,人工2小时"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190522/0e36975c84e6e3fb0e576556a1168330.png", "独家!天才少年 Vitalik:“中国开发者应多关注以太坊!”"));
        mBannerList.add(new BannerEntity(id++, "https://csdnimg.cn/feed/20190618/8aee33b2a4ef11b0fb70bf371484c2ee.jpg", "不是码农,不会敲代码的她,却最懂程序员!| 人物志"));

        //记录原始数据大小,表示指示圆点数量。在下面特殊情况处理之前。
        mOriSize = mBannerList.size();

        //处理等于 2 的情况。
        if (mBannerList.size() == 2) {
            BannerEntity clone0 = mBannerList.get(0).clone();
            BannerEntity clone1 = mBannerList.get(1).clone();
            mBannerList.add(clone0);
            mBannerList.add(clone1);
        }
    }


    @Override
    public void initView() {

        mHandler = new MHandler();

        mFlBannerFrame = (FrameLayout) findViewById(R.id.fl_banner_frame);
        mLlBannerDotGroup = (LinearLayout) findViewById(R.id.ll_banner_dot_group);
        mVpBanner = ((ViewPager) findViewById(R.id.vp_banner));
        mPagerAdapter = new BannerPagerAdapter();
        mVpBanner.setAdapter(mPagerAdapter);

        initBanner();
    }

    /**
     * 初始化轮播图。
     */
    private void initBanner() {
        mFlBannerFrame.setVisibility(View.VISIBLE);
        mLlBannerDotGroup.setVisibility(View.VISIBLE);
        if (mBannerList.size() > 1) {
            //根据原始数据大小创建指示圆点
            for (int i = 0; i < mOriSize; i++) {
                CheckedTextView rbDot = (CheckedTextView) getLayoutInflater().inflate(R.layout.view_banner_dot_round, mLlBannerDotGroup, false);
                mLlBannerDotGroup.addView(rbDot);
                if (i == 0) {
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rbDot.getLayoutParams();
                    lp.leftMargin = 0;
                }
            }
            //设置当前页为 1 下标页
            mVpBanner.setCurrentItem(1);
            //第一个圆点高亮
            ((CheckedTextView) mLlBannerDotGroup.getChildAt(0)).setChecked(true);

            //开始轮播
            startAutoScroll();
        } else if (mBannerList.size() == 1) {
            mVpBanner.setCurrentItem(0);
            mLlBannerDotGroup.setVisibility(View.GONE);
        } else {
            mFlBannerFrame.setVisibility(View.GONE);
        }
    }

    class BannerPagerAdapter extends PagerAdapter {

        private ArrayList<BannerEntity> mDataList = new ArrayList<>();

        public BannerPagerAdapter() {
            resetData(0);
        }

        public void resetData(int position) {
            if (mBannerList.size() > 1) {
                int leftPos = (mBannerList.size() + position - 1) % mBannerList.size();
                int rightPos = (position + 1) % mBannerList.size();
                mDataList.clear();
                mDataList.add(mBannerList.get(leftPos));
                mDataList.add(mBannerList.get(position));
                mDataList.add(mBannerList.get(rightPos));
            } else if (mBannerList.size() == 1) {
                mDataList.add(mBannerList.get(position));
            }

        }

        public BannerEntity getItem(int position) {
            return mDataList.get(position);
        }

        @Override
        public int getCount() {
            return mDataList.size();
        }

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

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            BannerEntity bannerEntity = mDataList.get(position);
            View view = LayoutInflater.from(container.getContext()).inflate(R.layout.layout_banner_item, container, false);
            ImageView ivBannerImg = view.findViewById(R.id.iv_banner_img);
            TextView tvBannerDesc = view.findViewById(R.id.tv_banner_desc);
            Glide.with(BannerActivity.this).load(bannerEntity.getImgUrl()).centerCrop().into(ivBannerImg);
            tvBannerDesc.setText(bannerEntity.getDesc());
            view.setTag(bannerEntity);
            container.addView(view);
            return bannerEntity;
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            container.removeView(container.findViewWithTag(object));
        }

        public void updateData() {
            if (mDataList.size() > 1) {
                //传入 mBannerList 中的位置
                resetData(mBannerList.indexOf(mDataList.get(mVpBanner.getCurrentItem())));
                notifyDataSetChanged();
            }
        }

        @Override
        public int getItemPosition(@NonNull Object object) {
            if (mDataList.contains(object)) {
                return mDataList.indexOf(object);
            } else {
                return POSITION_NONE;
            }
        }
    }


    @SuppressLint("ClickableViewAccessibility")
    @Override
    public void setListeners() {
        mVpBanner.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            private int mCheckedId = 0;

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);

                ((CheckedTextView) mLlBannerDotGroup.getChildAt(mCheckedId)).setChecked(false);
                //设置圆点高亮
                int id = mPagerAdapter.getItem(position).getId();
                ((CheckedTextView) mLlBannerDotGroup.getChildAt(id)).setChecked(true);

                mCheckedId = id;

            }

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (positionOffset == 0) {
                    mPagerAdapter.updateData();
                }
            }
        });

        mVpBanner.setOnTouchListener(new View.OnTouchListener() {

            float currX = 0;
            float dx = 0;
            long timeMillis = 0;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        currX = event.getX();
                        timeMillis = System.currentTimeMillis();
                        stopAutoScroll();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float x = event.getX();
                        dx += Math.abs(x - currX);
                        currX = x;
                        break;
                    case MotionEvent.ACTION_UP:
                        if (dx < 20 && (System.currentTimeMillis() - timeMillis) < 800) {
                            int currentItem = mVpBanner.getCurrentItem();
                            BannerEntity item = mPagerAdapter.getItem(currentItem);
                            Toast.makeText(BannerActivity.this, item.getDesc(), Toast.LENGTH_SHORT).show();
                        }
                        startAutoScroll();
                        dx = 0;
                        break;
                    case MotionEvent.ACTION_CANCEL:
                        dx = 0;
                        startAutoScroll();
                        break;
                }
                return false;
            }
        });

    }

    private void startAutoScroll() {
        mHandler.sendEmptyMessageDelayed(WHAT_AUTO_SCROLL, 2000);
    }

    private void stopAutoScroll() {
        mHandler.removeMessages(WHAT_AUTO_SCROLL);
    }

    @Override
    public void onClick(View v) {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopAutoScroll();
    }
}

 

5 参考

 

源码:
https://github.com/wangzhengyangNo1/ViewPagerSerialDemo

 

已于2021-3-1 17:13:20修改
1
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐