用RecyclerView打造一個(gè)輪播圖(進(jìn)階版)

姚麗冰 學(xué)號(hào):16050120089

轉(zhuǎn)載:

https://juejin.im/entry/5a154b21f265da431d3c53d6

【嵌牛導(dǎo)讀】:前幾天寫了篇《用RecyclerView打造一個(gè)輪播圖》(以下簡(jiǎn)稱基礎(chǔ)版),看到有讀者評(píng)論說相比Viewpager,用RecyclerView看起來沒什么特別的優(yōu)勢(shì)。究其原因,目前只用到了RecyclerView最基礎(chǔ)的一部分功能。其實(shí)相比Viewpager實(shí)現(xiàn)的輪播圖,RecyclerView版的最大優(yōu)勢(shì)就在于它的靈活多變性,可定制性高。本篇文章將通過利用LayoutManger、SnapHelper等RecyclerView的輔助類來實(shí)現(xiàn)一系列更為炫酷的輪播圖。

【嵌牛鼻子】:RecyclerView,Viewpager,youtManger,SnapHelper,輪播圖

【嵌牛提問】:如何利用LayoutManger、SnapHelper等RecyclerView的輔助類來實(shí)現(xiàn)一系列更為炫酷的輪播圖。

【嵌牛正文】:

初試:豎直輪播圖

基礎(chǔ)版中,RecyclerView設(shè)置了默認(rèn)橫向的LinearLayoutManager:

LinearLayoutManager indicatorLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);

那么我們?cè)賮砑觽€(gè)屬性:

<attr name="orientation" format="enum">

? ? ? ? ? ? <enum name="horizontal" value="0"/>

? ? ? ? ? ? <enum name="vertical" value="1" />

</attr>

同時(shí)在布局文件中設(shè)置app:orientation="vertical",讓LinearLayoutManager的布局方向變?yōu)樨Q直(為了保持統(tǒng)一,標(biāo)示點(diǎn)也設(shè)置為同一方向),就是這么簡(jiǎn)單,一款豎直滑動(dòng)的無限輪播圖就打造完成了!

實(shí)戰(zhàn):仿魅族輪播圖

之前有篇文章ViewPager系列之 仿魅族應(yīng)用的廣告BannerView,不過這是用ViewPager實(shí)現(xiàn)的,那我們就來個(gè)RecyclerView版的,而實(shí)現(xiàn)重點(diǎn)的就在于自定義LayoutManger(如果不太了解這部分的知識(shí),請(qǐng)先移步學(xué)習(xí)下(╯︵╰))。

這次先上成果圖,再慢慢分析:

以上的效果僅僅是換了一個(gè)LayoutManger和一個(gè)itemview(為了顯示效果,imageview外面套了cardview)。

首先我們做準(zhǔn)備工作,定義幾個(gè)常量:

private? float SCALE_RATE ;當(dāng)前圖片放大比例

private? int mOrientation;布局方向(HORIZONTAL or VERTICAL)

private? int itemSpace;圖片之間的間距

自定layoutmanager第一步當(dāng)然是實(shí)現(xiàn)唯一必須要實(shí)現(xiàn)的方法:

? @Override

? ? public RecyclerView.LayoutParams generateDefaultLayoutParams() {

? ? ? ? return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,

? ? ? ? ? ? ? ? ViewGroup.LayoutParams.WRAP_CONTENT);

? ? }

然而并沒有什么用,99%的自定義layoutmanager都是這么寫的,因?yàn)槲覀儧]有把view添加到 RecyclerView中。所以接下來就是重寫onLayoutChildren()來進(jìn)行布局,這個(gè)方法的作用相當(dāng)于普通Viewgroup中的onLayout()方法,在RecyclerView滾動(dòng),數(shù)據(jù)改變等情況都會(huì)調(diào)用此方法來重新布局。

@Override

? ? public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

? ? ? ? if (state.getItemCount() == 0) {

? ? ? ? ? ? removeAndRecycleAllViews(recycler);

? ? ? ? ? ? mOffset = 0;

? ? ? ? ? ? return;

? ? ? ? }//沒有Itemitemview,不做處理

? ? ? ? ensureLayoutState();

? ? ? ? if (getChildCount() == 0) {//沒有可見的itemview,初始化需要用到的一些參數(shù)

? ? ? ? ? ? View scrap = recycler.getViewForPosition(0);

? ? ? ? ? ? measureChildWithMargins(scrap, 0, 0);

? ? ? ? ? ? mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);

? ? ? ? ? ? mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);

? ? ? ? ? ? mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;

? ? ? ? ? ? mSpaceInOther = (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;

? ? ? ? ? ? mInterval = setInterval();

? ? ? ? ? ? setUp();

? ? ? ? ? ? mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1;

? ? ? ? ? ? mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1;

? ? ? ? }

? ? ? ? if (mPendingScrollPosition != NO_POSITION) {

? ? ? ? ? ? mOffset = mReverseLayout ?

? ? ? ? ? ? ? ? ? ? mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval;

? ? ? ? }

? ? ? ? //開始布局

? ? ? ? detachAndScrapAttachedViews(recycler);

? ? ? ? layoutItems(recycler);

? ? }

上面只是做一些初始化工作,接下來是layoutItems方法,就貼一些重要代碼:

取當(dāng)前可見的view進(jìn)行放置,遍歷計(jì)算位置

final int currentPos = mReverseLayout ?

? ? ? ? ? ? ? ? -getCurrentPositionOffset() : getCurrentPositionOffset();

? ? ? ? int start = currentPos - mLeftItems;

? ? ? ? int end = currentPos + mRightItems;

? for (int i = start; i < end; i++) {

? ? if (findViewByPosition(adapterPosition) == null) {

? ? ? ? ? ? ? ? ? ? final View scrap = recycler.getViewForPosition(adapterPosition);

? ? ? ? ? ? ? ? ? ? measureChildWithMargins(scrap, 0, 0);

? ? ? ? ? ? ? ? ? ? resetViewProperty(scrap);

? ? ? ? ? ? ? ? ? ? final float targetOffset = getProperty(i) - mOffset;

? ? ? ? ? ? ? ? ? ? layoutScrap(scrap, targetOffset);

具體的布局方法,主要就是,回收不可見的itemview,遍歷可見的itemview進(jìn)行位置計(jì)算并放置:

private void layoutScrap(View scrap, float targetOffset) {

? ? ? ? final int left = calItemLeft(scrap, targetOffset);

? ? ? ? final int top = calItemTop(scrap, targetOffset);

? ? ? ? if (mOrientation == VERTICAL) {

? ? ? ? ? ? layoutDecorated(scrap, mSpaceInOther + left, mSpaceMain + top,

? ? ? ? ? ? ? ? ? ? mSpaceInOther + left + mDecoratedMeasurementInOther, mSpaceMain + top + mDecoratedMeasurement);

? ? ? ? } else {

? ? ? ? ? ? layoutDecorated(scrap, mSpaceMain + left, mSpaceInOther + top,

? ? ? ? ? ? ? ? ? ? mSpaceMain + left + mDecoratedMeasurement, mSpaceInOther + top + mDecoratedMeasurementInOther);

? ? ? ? }

? ? ? ? setItemViewProperty(scrap, targetOffset);

? ? }

? ? //在滾動(dòng)時(shí)根據(jù)距離動(dòng)態(tài)縮放itemView(在這里你可以自定義滑動(dòng)動(dòng)畫,改變 itemView的屬性,透明度,大小,角度等等)

? ? private void setItemViewProperty(View itemView, float targetOffset) {

? ? ? ? float scale = calculateScale(targetOffset + mSpaceMain);

? ? ? ? itemView.setScaleX(scale);

? ? ? ? itemView.setScaleY(scale);

? ? }

接下來是處理滾動(dòng),讓recyclerview可以滾動(dòng)起來:

? @Override

? ? public boolean canScrollHorizontally() {

? ? ? ? return mOrientation == HORIZONTAL;

? ? }

? ? @Override

? ? public boolean canScrollVertically() {

? ? ? ? return mOrientation == VERTICAL;

? ? }

@Override

? ? public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {

? ? ? ? //位移0、沒有子View 當(dāng)然不移動(dòng)

? ? ? if (getChildCount() == 0 || dy == 0) {

? ? ? ? ? ? return 0;

? ? ? ? }

? ? ? ? ? int willScroll = dy;

? ? ? ? ? float realDx = dy / getDistanceRatio();//真正滑動(dòng)的距離

? ? ? // 從新布局可見的view

? ? ? ? for (int i = 0; i < getChildCount(); i++) {

? ? ? ? ? ? final View scrap = getChildAt(i);

? ? ? ? ? ? final float delta = propertyChangeWhenScroll(scrap) - realDx;

? ? ? ? ? ? layoutScrap(scrap, delta);

? ? ? ? }

? ? }

因?yàn)樵谖覀冊(cè)诓季趾蜐L動(dòng)時(shí)考慮了橫向和豎向的情況,所以設(shè)置豎直的無限輪播圖也很簡(jiǎn)單:

new BannerLayoutManager(BannerLayoutManager.VERTICAL, Util.dp2px(10));(方向豎直,圖片間距10dp)

總結(jié)

看到這,我想大家都能看到用RecyclerView實(shí)現(xiàn)無限輪播圖的強(qiáng)大的指出了吧:

adapter可以處理無限輪播;layoutmanager可以處理布局和滑動(dòng)動(dòng)畫;SnapHelper可以讓iremview滑動(dòng)起來像viewpager一樣(一般用PagerSnapHelper就行了)。

而上面的所有動(dòng)畫效果僅僅都是通過改動(dòng)layoutmanager,然后再通過設(shè)置不同itemview,就可以做出各種效果。最后感謝以下文章,上面的實(shí)現(xiàn)思路大部分來自于他們。

github地址

參考資料

http://blog.csdn.net/zxt0601/article/details/52956504

http://www.lxweimin.com/p/7bb7556bbe10

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 前言 前幾天寫了篇《用RecyclerView打造一個(gè)輪播圖》(以下簡(jiǎn)稱基礎(chǔ)版),看到有讀者評(píng)論說相比Viewpa...
    大頭呆閱讀 6,052評(píng)論 1 13
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,142評(píng)論 25 708
  • 這篇文章分三個(gè)部分,簡(jiǎn)單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,413評(píng)論 0 27
  • 在高峰時(shí),就要打算 怎么走下坡路了
    胡一飄閱讀 87評(píng)論 0 0
  • 餃子店里聽見了這么一句話"我來這里都快四年了"。聽到這句的我不禁在想,到底是什么,讓人可以堅(jiān)持留在深圳四年,而我僅...
    嘉雯雯Karmen閱讀 242評(píng)論 0 0