ViewPager封裝輪播效果+指示器 實(shí)現(xiàn)一行代碼展示輪播圖

?


無量變何以質(zhì)變

?
?


概述

平時(shí)應(yīng)用開發(fā)中首頁經(jīng)常會(huì)有一個(gè)Banner輪播的展示,不可避免的需要封裝一個(gè)自定義View,在使用的時(shí)候能夠方便的只用一句代碼設(shè)置圖片地址集合,就可以啟動(dòng)輪播效果,本文將通過ViewPager一步步對輪播圖進(jìn)行實(shí)現(xiàn),最終效果如下:

輪播.gif

?

需要定制的特性

1.是否顯示指示器

2.指示器圓點(diǎn)大小、間距

3.輪播自動(dòng)切換的間隔時(shí)長

4.是否自動(dòng)輪播

5.左右無邊界輪播

:目前暫時(shí)只支持url的圖片形式,其他形式例如本地圖片可自行在Adapter中instantiateItem方法進(jìn)行調(diào)整,且本文的圖片展示是依賴Glide來展示。
?

實(shí)現(xiàn)思路

我們都知道ViewPager是可以左右滑動(dòng)的,并且可以設(shè)置Adapter, 那如果能夠設(shè)置為無限大,且每隔一段時(shí)間就調(diào)用滑動(dòng),即可達(dá)到輪播效果,主要需要解決以下幾個(gè)問題:如何實(shí)現(xiàn)無限循環(huán),自動(dòng)輪播以及兼容手勢滑動(dòng)。


1)實(shí)現(xiàn)無限循環(huán)

首先實(shí)現(xiàn)左右無限循環(huán)的效果,思路就是將ViewPager的getCount()返回為Integer.MAX_VALUE,然后在ViewPager每次切換Item的時(shí)候,會(huì)調(diào)用PagerAdapter的instantiateItem方法,這個(gè)方法返回當(dāng)前準(zhǔn)備切換到的下標(biāo),由于我們設(shè)置的Item數(shù)量是Integer.MAX_VALUE,因此這里返回的下標(biāo)有可能是0-2147483647。

假設(shè)我們要顯示的banner圖總共有3張,那在第3張即將切換到第1張的時(shí)候,需要重新將position置為0,因此可以采用對下標(biāo)取余的方式讓position可以一直處于0-3的無限循環(huán)之中。

關(guān)鍵代碼如下:


private class InnerPagerAdapter extends PagerAdapter {

        public InnerPagerAdapter() {}


        @Override
        public int getCount() {
            return Integer.MAX_VALUE;
        }


        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0 == arg1;
        }

 

        @Override
        public Object instantiateItem(ViewGroup container, int position) {

            position %= mBannerUrlList.size();

            if (position < 0) {
                position = mBannerUrlList.size() + position;
            }

            ImageView bannerIv = new ImageView(getContext());

            bannerIv.setScaleType(ImageView.ScaleType.CENTER_CROP);

            bannerIv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

            Glide.with(getContext()).load(mBannerUrlList.get(position)).into(bannerIv);

            container.addView(bannerIv);

            //bannerIv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

            return bannerIv;
        }


        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
        }

}

?
?


2)實(shí)現(xiàn)自動(dòng)輪播

實(shí)現(xiàn)了左右無限輪播,接下來就要讓它能夠自動(dòng)動(dòng)起來,這里采用handler的方式,每次將下標(biāo)+1之后setCurrentItem為新的下標(biāo),關(guān)鍵代碼如下:


/**
 * 是否自動(dòng)滑動(dòng)
 */
private boolean mIsAutoScroll = true;

/**
 * 默認(rèn)頁面之間自動(dòng)切換的時(shí)間間隔
 */
private long mDelayTime = 2000;

//播放標(biāo)志
private boolean isPlay = false;

//觸發(fā)輪播的消息標(biāo)志位
private final int PLAY = 0x123;

/**
 * 輪播計(jì)時(shí)器
 */
private Handler mHandler = new Handler() {

    public void handleMessage(android.os.Message msg) {
        if (msg.what == PLAY && mIsAutoScroll) {
            mBannerViewPager.setCurrentItem(mCurrentIndex);

            if (isPlay) {
                play();
            }
        }
    }
};


public void startPlay(long delayMillis) {
    isPlay = true;
    mDelayTime = delayMillis;
    play();
}

private void play() {
    mCurrentIndex++;
    mHandler.sendEmptyMessageDelayed(PLAY, mDelayTime);
}

?
?


3)兼容手勢滑動(dòng)

實(shí)現(xiàn)了自動(dòng)輪播之后,我們還要兼容用戶主動(dòng)滑動(dòng)的場景,即用戶主動(dòng)滑動(dòng)時(shí),應(yīng)該暫停自動(dòng)輪詢,先以用戶主動(dòng)滑動(dòng)的動(dòng)作為主,當(dāng)用戶放開手指之后,再重新繼續(xù)自動(dòng)輪詢,既然要區(qū)分是否是手勢滑動(dòng),可以在ViewPager的滑動(dòng)監(jiān)聽接口onPageScrollStateChange中去判斷,如下:

/**
  * ViewPager滑動(dòng)監(jiān)聽
  */
ViewPager.OnPageChangeListener mPageListener = new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        onPageScrollStateChange(state);
    }
};

 /**
   * 手指滑動(dòng)時(shí)暫停自動(dòng)輪播,手指松開時(shí)重新啟動(dòng)自動(dòng)輪播
   *
   * @param state
   */
private void onPageScrollStateChange(int state) {
    if (!mIsAutoScroll) {
        return;
    }
    switch (state) {
        case ViewPager.SCROLL_STATE_IDLE:
            if (!mGestureScroll) {
                return;
            }
            mGestureScroll = false;
            mHandler.removeMessages(PLAY);
            mHandler.sendEmptyMessageDelayed(PLAY, 100);
            break;
        case ViewPager.SCROLL_STATE_DRAGGING:
            // 手指滑動(dòng)時(shí),清除播放下一張,防止滑動(dòng)過程中自動(dòng)播放下一張
            mGestureScroll = true;
            mHandler.removeMessages(PLAY);
            break;
        default:
            break;
    }
}

ViewPager的SCRPLL_STATE_IDLE狀態(tài)是表示用戶手指松開時(shí),這個(gè)時(shí)候就繼續(xù)sendMessage啟動(dòng)輪詢,SCROLL_STATE_DRAGGING狀態(tài)表示用戶手指正在滑動(dòng)過程中,removeMessage將當(dāng)前的輪詢暫停

?
?


4)添加輪播指示器

以上完成了ViewPager的部分,實(shí)現(xiàn)了輪播圖的自動(dòng)輪詢效果,但一般輪播圖都會(huì)有個(gè)小小的指示器來讓用戶感知當(dāng)前處于哪個(gè)banner,一共有多少個(gè)banner,所以我們還需套自定義一個(gè)指示器View

/**
  * 指示器View
 */
public class BannerIndicator extends View{

    private int mCellCount;
    private int currentPosition;
    private Paint mPaint;
    /**
     * 指示器小圓點(diǎn)半徑
     */
    private int mCellRadius = dp2px(3);
    /**
     * 指示器小圓點(diǎn)間距
     */
    private int mCellMargin = dp2px(4);
    /**
     * 指示器小圓點(diǎn)激活狀態(tài)的顏色
     */
    private int mIndicatorColor = Color.parseColor("#000000");

    public BannerIndicator(Context context) {
        super(context);
        init();
    }

    public void init(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    public void setCellCount(int cellCount) {
        mCellCount = cellCount;
        invalidate();
    }

    public void setCurrentPosition(int currentPosition) {
        this.currentPosition = currentPosition;
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 重新測量當(dāng)前界面的寬度
        int width = getPaddingLeft() + getPaddingRight() + mCellRadius * 2 * mCellCount + mCellMargin * (mCellCount - 1);
        int height = getPaddingTop() + getPaddingBottom() + mCellRadius * 2;
        width = resolveSize(width, widthMeasureSpec);
        height = resolveSize(height, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mCellCount; i++) {
            if (i == currentPosition) {
                mPaint.setColor(mIndicatorColor);
            } else {
                mPaint.setColor(Color.WHITE);
            }
            int left = getPaddingLeft() + i * mCellRadius * 2 + mCellMargin * i;

            canvas.drawCircle(left + mCellRadius, getHeight() / 2, mCellRadius, mPaint);
        }
    }
}

其實(shí)就是根據(jù)banner的數(shù)量來進(jìn)行繪制每個(gè)小圓圈的位置,并且用一個(gè)currentPosition來標(biāo)志當(dāng)前哪個(gè)小圓圈是處于激活的狀態(tài),顯示不同的顏色。在ViewPager每次回調(diào)onPageSelect的時(shí)候,將當(dāng)前的currentPosition也同步更新,并且調(diào)用invalidate觸發(fā)onDraw重新繪制。
?
?


5)開放滑動(dòng)監(jiān)聽接口

最后,雖然內(nèi)部實(shí)現(xiàn)了自動(dòng)輪播,但是我們還是要將滑動(dòng)切換的接口放開來

/**
 * 滾動(dòng)監(jiān)聽回調(diào)接口
 */

ScrollPageListener mScrollPageListener;

public void setScrollPageListener(ScrollPageListener mScrollPageListener) {
        this.mScrollPageListener = mScrollPageListener;
}

public interface ScrollPageListener {
    void onPageSelected(int position);
}
 

ViewPager.OnPageChangeListener mPageListener = new ViewPager.OnPageChangeListener() {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}

    @Override
    public void onPageSelected(int position) {
        if (mScrollPageListener != null) {
            mScrollPageListener.onPageSelected(smallPos);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        onPageScrollStateChange(state);
    }
};

?
?


6)提供設(shè)置Banner數(shù)據(jù)接口

提供一個(gè)供外界設(shè)置banner數(shù)據(jù)的方法:


/**
 * 設(shè)置Banner圖片地址數(shù)據(jù)
 * @param bannerData
 */
public void setBannerData(List<String> bannerData) {
    mBannerUrlList.clear();
    mBannerUrlList.addAll(bannerData);
    mAdapter.notifyDataSetChanged();
    startPlay(mDelayTime);
    mIndicator.setCellCount(bannerData.size());
}

?
?


應(yīng)用

xml布局中引用(如果是wrap_content,默認(rèn)是200dp的高度,可在自定義View的onMeasure中自行調(diào)整):

<com.example.zjy.zjywidget.banner.BannerView
        android:id="@+id/banner_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
</com.example.zjy.zjywidget.banner.BannerView>

從此之后實(shí)現(xiàn)Banner輪播效果就方便多了,只需要一句代碼即可:

mBannerView.setBannerData(getBannerData());

getBannerData就是你的圖片url集合,將其設(shè)置進(jìn)去,開啟你的自動(dòng)輪播吧~~~

?
?


后續(xù)

ViewPager不止可以做輪播,還支持自定義各種切換動(dòng)畫,詳見我另一篇文章 Android ViewPager多屏顯示、切換動(dòng)畫,讓你的輪播炫起來

最近有點(diǎn)沉迷于自定義View,其實(shí)很多看似很基礎(chǔ)的東西還是很重要的,底層基礎(chǔ)決定上層建筑,本篇的輪播效果盡管并不復(fù)雜,但是巧妙利用View的屬性有時(shí)候能夠帶來不錯(cuò)的效果。這里還存在一個(gè)無限大所導(dǎo)致的內(nèi)存隱患問題,不知道廣大簡友有沒有更好的方案進(jìn)行優(yōu)化?

源碼傳送門GitHub-ZJYWidget-YCircleProgressBar
CSDN博客IT_ZJYANG
簡??????????書Android小Y

里面還有很多實(shí)用的自定義View源碼及demo,會(huì)長期維護(hù),歡迎Star~ 如有不足之處或建議還望指正,相互學(xué)習(xí),相互進(jìn)步,如果覺得不錯(cuò)動(dòng)動(dòng)小手點(diǎn)個(gè)喜歡, 謝謝~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容