Android輪播圖效果的各種實現

前言

很多APP的首頁通常會有一個帶有動畫切換的各種輪播圖效果,剛好新項目中也要實現輪播圖的效果,于是便研究了Android平臺下各種輪播效果,網上也有很多實現輪播相關的方案,但是質量參差不齊,為此踩了不少的坑。下面就來關于輪播圖實現方面的一些學習心得,希望對大家有所幫助。

**(一)使用ViewPager實現輪播圖切換效果 **

開源項目:Android-Coverflow
GitHub地址:https://github.com/crosswall/Android-Coverflow
其效果如下:

Gif_20170430_114547.gif

**(1)ViewPager切換動畫實現原理 **

  • 使用View.PageTransformer實現動畫切換效果,ViewPager的動畫切換效果都是重寫PageTransformer這個類來實現的。PageTransformer方法中就一個transformPage方法,里面有兩個參數,viewPager的滑動時的子View,這個很好理解,最難理解的是這個position參數。切換時的動畫實現主要靠position來顯示。下面來介紹其中含義。
 public interface PageTransformer {
        void transformPage(View page, float position);
    }

ViewPager左右切換時,position的值范圍說明:

  • **(-oo,-1) 相對于左邊第一頁,其左邊的所有頁面 **
  • *** [-1, 0 ) 相對于當前選中頁,其左邊的第一頁**
  • *** [0, 1 ) 相對于當前選中頁,其右邊第一頁 **
  • [1,+oo) 相對于右邊第一頁,其右邊的所有頁面

下面舉例說明:

  • **當前ViewPage選中的頁為,其右邊的頁面為B,現在向左滑動A,慢慢由頁面A切換到頁面B **

**頁面A的position值是由 0 慢慢減小到 -1 [0,-1] **
**頁面B的position值是由 1 慢慢減小到 0 [1,0] **

**此時頁面B為ViewPage當前選中的頁面 **

  • **再向右滑動頁面B,慢慢由頁面B切換到頁面A **

**頁面A的position值由 -1 慢慢增加到 0 [-1,0] **
頁面B的position值由 0 慢慢增加到 1 [ 0,1]

理解了transformPage方法中position的含義,那么ViewPager動畫切換效果的實現就很好理解了。下面是項目中我實現的一個ViewPager切換效果,如果所示:

代碼:

public class ZoomPageTransformer implements ViewPager.PageTransformer {
    private static final float MAX_SCALE = 1.0f;

    private static final float MIN_SCALE = 0.85f;//0.85f

    private static final  float MIN_ALPHA = 0.3f;

    private static final String TAG = "PageTransformer";
    @Override
    public void transformPage(View view, float position) {
        //setScaleY只支持api11以上
        if (position < -1) {
            view.setScaleX(MIN_SCALE);
            view.setScaleY(MIN_SCALE);
            view.setAlpha(MIN_ALPHA);//左邊的左邊的Page
        } else if (position <= 1) {
            float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
            if (position > 0) {
                view.setTranslationX(-scaleFactor);
            } else if (position < 0) {
                view.setTranslationX(scaleFactor);
            }
            view.setScaleY(scaleFactor);
            view.setScaleX(scaleFactor);

           // float alpha = 1f -  Math.abs(position) * (1 - );

            float alpha = MIN_ALPHA + (1 - MIN_ALPHA) * (1 - Math.abs(position));
            view.setAlpha(alpha);

            Log.i(TAG,"position = " + position + " alpha = " + alpha);

        } else { // (1,+Infinity]

            view.setScaleX(MIN_SCALE);
            view.setScaleY(MIN_SCALE);
            view.setAlpha(MIN_ALPHA);
        }
    }
}

當position的范圍在(-oo,-1) 和[1,+oo)時,view的比例縮小MIN_SCALE(0.85f),透明度縮小到 MIN_ALPHA(0.3f)

當position的范圍在 [-1,1]的時候,View的scale值和position絕對值成反比

 float scaleFactor = MIN_SCALE + (1 - Math.abs(position)) * (MAX_SCALE - MIN_SCALE);
            if (position > 0) {
                view.setTranslationX(-scaleFactor);
            } else if (position < 0) {
                view.setTranslationX(scaleFactor);
            }
            view.setScaleY(scaleFactor);
            view.setScaleX(scaleFactor);

  • 當position的值等于-1或者1的時候,此時View就是左邊第一頁和右邊第一頁,此時scale值就是MIN_SCALE(0.85f)

  • 當position的值范圍在[-1,0]時,scale的值慢慢由MIN_SCALE變大到MAX_SCALE,也就是說ViewPager右滑動時,左邊第一個View是慢慢放大的,直到其放大到MAX_SCALE。

  • 當position的值范圍在(0,1]時,scale的值由MAX_SCALE變小到MIN_SCALE,也就是說ViewPager右滑動時,右邊第一個View是慢慢縮小的,直到其比例縮放到MIN_SCALE。

透明度變化也是同樣一個原理。

**(2)讓ViewPager顯示多頁 **

一般情況下,ViewPager只能顯示一頁,那如何讓其顯示多個子頁面呢?那就不得不說setClipChildren(false)這個方法了。

  • 默認情況下為setClipChildren(true),如果子View的布局范圍超過了父View,那么它的邊界將會被裁減掉,也就是說超過父View的部分是看不到的。
  • 當setClipChildren(false)的情況下,子View的布局范圍超過了父View部分將不會被裁減掉 ,而將會以動畫的形式顯示出來。
     <me.crosswall.lib.coverflow.core.PagerContainer
        android:id="@+id/pager_container"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:clipChildren="false"
        android:background="?attr/colorPrimary">

        <android.support.v4.view.ViewPager
          android:id="@+id/overlap_pager"
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:layout_gravity="center" />

    </me.crosswall.lib.coverflow.core.PagerContainer>

Layout.xml

<com.mtime.cinema.business.recommend.widget.BannerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/recommend_banner_height"
    android:clipChildren="false">

    <android.support.v4.view.ViewPager
        android:id="@+id/fragment_recommend_viewPager"
        android:layout_width="@dimen/recommend_banner_image_width"
        android:layout_height="@dimen/recommend_banner_image_height"
        android:layout_centerHorizontal="true"
        android:clipChildren="false" />

    <LinearLayout
        android:id="@+id/fragment_recommend_banner_indicator"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal" />


</com.mtime.cinema.business.recommend.widget.BannerView>

JAVA代碼

/**
 * Created by liuyu on 2017/4/17.
 * 推薦頁輪播圖控件
 */

public class BannerView extends RelativeLayout {

    private static final String TAG = "BannerView";

    private LinearLayout mBannerIndicator;

    private ViewPager mViewPager;

    private long VIEWPAGER_SWITCH_DURING = 8000;//輪播時間

    private int MSG_START_SCROLL = 100;//消息的名稱

    private int mPointRadius;

    private int mPointTotalCount;//小圓點真正個數,有可能接口返回數據記錄 < DEFAULT_POINT_COUNT

    private Drawable mNormalColor, mSelectedColor;

    private BannerAdapter mBannerAdapter;
    private int mPointMarginTop;

    public BannerView(Context context) {
        super(context);
        initBanner();
    }

    public BannerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initBanner();
    }

    private void initBanner() {
        this.setClipChildren(false);
        mPointRadius = getContext().getResources().getDimensionPixelSize(R.dimen.recommend_banner_point_radius);
        mPointMarginTop = getContext().getResources().getDimensionPixelSize(R.dimen.recommend_banner_point_margin_top);

        mNormalColor = getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_normal);
        mSelectedColor = getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_selected);
    }

    public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initBanner();
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG_START_SCROLL) {
                if (mHandler != null) {
                    mHandler.removeMessages(MSG_START_SCROLL);
                    mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
                    mHandler.sendEmptyMessageDelayed(MSG_START_SCROLL, VIEWPAGER_SWITCH_DURING);
                }
            }
        }
    };


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mBannerIndicator = (LinearLayout) findViewById(R.id.fragment_recommend_banner_indicator);
        mViewPager = (ViewPager) findViewById(R.id.fragment_recommend_viewPager);
    }

    /**
     * 添加小圓點
     */
    private void addDots(int count) {
        mPointTotalCount = count;
        // mBannerIndicator.removeAllViews();
        Logger.i(TAG, "addDots count = " + count);
        //加點
        for (int i = 0; i < count; i++) {
            ImageView pointView = new ImageView(getContext());

            pointView.setImageDrawable(getResources().getDrawable(R.drawable.bg_shape_recommend_banner_point_normal));
            //點的大小
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mPointRadius * 2, mPointRadius * 2);
            //點的間隔
            layoutParams.leftMargin = mPointRadius * 2;
            //居中顯示
            layoutParams.topMargin = mPointMarginTop;

            Logger.i(TAG, "addDots index = " + i);

            pointView.setLayoutParams(layoutParams);
            //把點添加到容器中
            mBannerIndicator.addView(pointView);
        }

    }

    /**
     * @param dataList
     */
    public void show(List<BannerBean> dataList) {

        Logger.i(TAG, "start show banner");

        if (dataList == null || dataList.size() == 0) {
            return;
        }

      /*  if (mViewPager.getChildCount() > 0 && mBannerIndicator.getChildCount() > 0) {
            mBannerAdapter.notifyDataSetChanged();
        }*/


        //重置數據

        if (mViewPager.getChildCount() > 0) {
            mViewPager.removeAllViews();
        }

        if (mBannerIndicator.getChildCount() > 0) {
            mBannerIndicator.removeAllViews();
        }


        addDots(dataList.size());

        /**** 重要部分  ******/
        //clipChild用來定義他的子控件是否要在他應有的邊界內進行繪制。 默認情況下,clipChild被設置為true。 也就是不允許進行擴展繪制。
        mViewPager.setClipChildren(false);
        //父容器一定要設置這個,否則看不出效果


        //mViewPager.setPageMargin(getResources().getDimensionPixelOffset(R.dimen.recommend_banner_image_space));

        mViewPager.setOffscreenPageLimit(5);


        final List<String> imageList = new ArrayList<>();
      //add image url

        mBannerAdapter = new BannerAdapter(getContext(), dataList);
        mBannerAdapter.setImageList(imageList);

        mViewPager.setAdapter(mBannerAdapter);

        int currentItem = Integer.MAX_VALUE / 2;

        mViewPager.setCurrentItem(currentItem);

        changeIndicatorStatus(currentItem);

        //設置ViewPager切換效果,即實現畫廊效果
        mViewPager.setPageTransformer(true, new ZoomPageTransformer());


        //將容器的觸摸事件反饋給ViewPager
        this.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // dispatch the events to the ViewPager, to solve the problem that we can swipe only the middle view.
                return mViewPager.dispatchTouchEvent(event);
            }

        });
        //圖片數量大于1的時候,才進行自動輪播
        if (dataList.size() > 1) {
            startAutoScroll();
        }

        mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                changeIndicatorStatus(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

    }


    /**
     * 開始自動切換
     */
    public void startAutoScroll() {
        if (mHandler != null) {
            mHandler.removeMessages(MSG_START_SCROLL);
            mHandler.sendEmptyMessageDelayed(MSG_START_SCROLL, VIEWPAGER_SWITCH_DURING);
        }
    }

    /**
     * 停止自動切換
     */
    public void stopScroll() {
        mHandler.removeMessages(MSG_START_SCROLL);
    }

    public void changeIndicatorStatus(int position) {
        if (position == 0) {
            return;
        }

        int realPos = position % mPointTotalCount;

        for (int i = 0; i < mPointTotalCount; i++) {
            if (i == realPos) {
                ((ImageView) mBannerIndicator.getChildAt(i)).setImageDrawable(mSelectedColor);
            } else {
                ((ImageView) mBannerIndicator.getChildAt(i)).setImageDrawable(mNormalColor);
            }
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        clear();
    }

    /**
     * 數據清理
     */
    public void clear() {
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
    }
}

(二)使用FancyCoverFlow實現輪播圖切換效果

Github地址:https://github.com/davidschreiber/FancyCoverFlow

(1) FancyCoverFlow的使用

  • Java代碼
    this.fancyCoverFlow.setUnselectedAlpha(0.0f);

         // 未選中的飽和度
         this.fancyCoverFlow.setUnselectedSaturation(0.0f);

         // 未選中的比例
         this.fancyCoverFlow.setUnselectedScale(0.8f);

         // child間距
         this.fancyCoverFlow.setSpacing(-60);

         // 旋轉度數
         this.fancyCoverFlow.setMaxRotation(0);

         // 非選中的重心偏移,負的向上
         this.fancyCoverFlow.setScaleDownGravity(-1f);

         // 作用距離
         this.fancyCoverFlow.setActionDistance(FancyCoverFlow.ACTION_DISTANCE_AUTO);
  • XML布局文件
<at.technikum.mti.fancycoverflow.FancyCoverFlow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        fcf:maxRotation="45"
        fcf:unselectedAlpha="0.3"
        fcf:unselectedSaturation="0.0"
        fcf:unselectedScale="0.4" />

(2) 實現無限循環效果

FancyCoverFlow是通過繼承Galley實現的,那么我們可以利用Galley的setSelection()方法實現一些特殊的效果,例如打造無限循環的錄播圖。

FancyCoverFlowSampleAdapter.java

public class FancyCoverFlowSampleAdapter extends FancyCoverFlowAdapter {

    // =============================================================================
    // Private members
    // =============================================================================

    private int[] images = {
            R.drawable.image1,  R.drawable.image2,  R.drawable.image3,
            R.drawable.image6, R.drawable.image5,  R.drawable.image4

           };

    // =============================================================================
    // Supertype overrides
    // =============================================================================

    @Override
    public Integer getItem(int i) {
        return images[ i];
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

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

    @Override
    public View getCoverFlowItem(int i, View reuseableView, ViewGroup viewGroup) {
        ImageView imageView = null;

        if (reuseableView != null) {
            imageView = (ImageView) reuseableView;
        } else {
            imageView = new ImageView(viewGroup.getContext());
            imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            imageView.setLayoutParams(new FancyCoverFlow.LayoutParams(300, 400));

        }

        imageView.setImageResource(this.getItem(i % images.length));
        return imageView;
    }


}

MainActivity.java


        this.fancyCoverFlow = (FancyCoverFlow) this.findViewById(R.id.fancyCoverFlow);

        this.fancyCoverFlow.setAdapter(new FancyCoverFlowSampleAdapter());
        this.fancyCoverFlow.setUnselectedAlpha(1.0f);
        this.fancyCoverFlow.setUnselectedSaturation(0.0f);
        this.fancyCoverFlow.setUnselectedScale(0.6f);
        this.fancyCoverFlow.setSpacing(-20);
        this.fancyCoverFlow.setMaxRotation(40);
        this.fancyCoverFlow.setScaleDownGravity(0.2f);
        this.fancyCoverFlow.setActionDistance(FancyCoverFlow.ACTION_DISTANCE_AUTO);
        this.fancyCoverFlow.setSelection(Integer.MAX_VALUE/2);

效果如下:

Gif_20170514_171512.gif

(三)使用RecyclerCoverFlow實現輪折疊的輪播圖效果

Github地址:https://github.com/ChenLittlePing/RecyclerCoverFlow

效果如下:

Gif_20170514_172656.gif

** (1) RecyclerCoverFlow的使用**

  • xml布局
    <recycler.coverflow.RecyclerCoverFlow
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    </recycler.coverflow.RecyclerCoverFlow>
  • Activity中引入
 mList = (RecyclerCoverFlow) findViewById(R.id.list);
    //        mList.setFlatFlow(true); //平面滾動
    mList.setAdapter(new Adapter(this));
    mList.setOnItemSelectedListener(new CoverFlowLayoutManger.OnSelected() {
        @Override
        public void onItemSelected(int position) {
            ((TextView)findViewById(R.id.index)).setText((position+1)+"/"+mList.getLayoutManager().getItemCount());
        }
    });

(2) 實現原理

RecyclerCoverFlow是通過集成RecyclerView實現的,由于默認情況下,ViewGroup中的子view繪制順序,index越大,其繪制順序會越靠后,所以后面的子View會遮住前面的子view,導致居中顯示的子View右邊重疊部分會被靠后的子View遮住。


    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        int center = getCoverFlowLayout().getCenterPosition()
                - getCoverFlowLayout().getFirstVisiblePosition(); //計算正在顯示的所有Item的中間位置
        if (center < 0) center = 0;
        else if (center > childCount) center = childCount;
        int order;
        if (i == center) {
            order = childCount - 1;
        } else if (i > center) {
            order = center + childCount - 1 - i;
        } else {
            order = i;
        }
        return order;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,775評論 25 708
  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,868評論 22 665
  • 前言 目前市場上的APP中,輪播圖可以說是很常見的。一個好的輪播圖,基本上適用于所有的APP。是時候打造一個自己的...
    帶心情去旅行閱讀 17,401評論 15 93
  • 使用vue-cli工具可以大大提高我們的開發效率,作為小白的喔首先遇到的問題就是:我寫到網頁怎么發布到服務器呢? ...
    碼農明明閱讀 2,669評論 4 9
  • 到過馬云演講現場或是在網絡看過馬云演講視頻的人都應該知道,馬云在中國所有的企業家中口才是一流的。比起馬化騰、李彥宏...
    勤勞的小盛閱讀 7,840評論 0 1