Banner廣告位是APP 中的一個很是重要的位置,爲何呢?由於它能帶來money。是一個公司很重要的一個營收點。像那種用戶數基數特別大的產品,如facebook、twitter、QQ、微信等等。Banner廣告位日營收估計得上千萬美刀(猜的,不知道具體數據)。一個漂亮的Banner每每可以吸引用戶的眼球,引導用戶點擊,從而提升轉化率。遺憾的是如今的大多數產品的Banner都是千篇一概的,沒有什麼亮點可言。可是前幾天在魅族手機上發現了一個效果不錯的Banner,魅族全部自家的APP所用的Banner 引發了個人注意。效果是這樣子的:java
meizuapp.gifandroid
看到這個Banner 第一眼就吸引了我,隨後就反覆的體驗了幾回了,感受這種Banner的效果還不錯。最後想着高仿一個和這種效果差很少的BannerView 。那麼本文就講一下如何實現這樣一個BannerView。最終實現的效果以下:git
MZBannerView.gifgithub
本文會講實現仿魅族Banner效果所要用到的一些關鍵知識點,目錄以下圖所示。全部的效果已經封裝成一個庫。詳細代碼請看github: https://github.com/pinguo-zhouwei/MZBannerView微信
本文目錄.pngapp
在開始實現魅族Banner效果以前,咱們先來整理一下實現一個BannerView的思路,首先須要用ViewPager,其次讓ViewPager無限輪播。其實BannerView就是一個無限輪播的ViewPager,而後作一些封裝處理,讓使用更加簡單就ok。ide
如今咱們在來看一下魅族的這個Banner。他與普通的banner的區別是當前頁顯示了前一頁和後一頁的部份內容。oop
ViewPager展現多頁.png佈局
拋開切換時的動畫先不說,要實現這個效果的第一步就是要讓ViewPager在一個頁面顯示多頁的內容(當前頁+先後頁部分)。post
1 . ViewPager展現多頁
要讓ViewPager頁面展現多頁的內容,就要用到ViewGroup的一個強大的屬性。這個屬性雖然強大,可是也不經常使用,可能有些小夥伴不知道(以前我也沒用過...),那就是clipChildren
屬性。這個屬性有什麼做用呢,咱們看一下它的文檔介紹:
/** * By default, children are clipped to their bounds before drawing. This * allows view groups to override this behavior for animations, etc. * * @param clipChildren true to clip children to their bounds, * false otherwise * @attr ref android.R.styleable#ViewGroup_clipChildren */
clipChildren: 默認值爲true, 子View 的大小隻能在父View規定的範圍以內,好比父View的高爲50,子View的高爲60 ,那麼多處的部分就會被裁剪。若是咱們設置這個值爲false的話,那麼多處的部分就不會被裁剪了。
這裏咱們就能夠利用這個屬性來實現了這個效果了,咱們設置ViewPager的父佈局的clipChildren
爲false。而後設置ViewPager 左右必定的邊距,那麼左右就空出了必定的區域,利用clipChildren
屬性,就能讓先後頁面的部分顯示在當前頁了。佈局以下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:clipChildren="false" android:orientation="vertical" > <android.support.v4.view.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginLeft="30dp" android:layout_marginRight="30dp" /> </LinearLayout>
這樣就能實現ViewPager 展現先後頁面的部份內容。
2 . 自定義ViewPager.PageTransformer動畫
上面實現了ViewPager當前頁面顯示先後頁的部份內容,可是從最開始魅族的Banner效果咱們能夠看出,滑動的時候是有 一個放大縮小的動畫的。左右顯示的部分有必定比例的縮小。這就要用到ViewPager.PageTransformer了。
ViewPager.PageTransformer 幹什麼的呢?ViewPager.PageTransformer 是用來作ViewPager切換動畫的,它是一個接口,裏面只有一個方法transformPage
。
public interface PageTransformer { /** * Apply a property transformation to the given page. * * @param page Apply the transformation to this page * @param position Position of page relative to the current front-and-center * position of the pager. 0 is front and center. 1 is one full * page position to the right, and -1 is one page position to the left. */ void transformPage(View page, float position); }
雖然只有一個方法,可是它很強大,它能反映出在ViewPager滑動過程當中,各個View的位置變化。咱們拿到了這些位置變化,就能在這個過程當中對View作各類各樣的動畫了。
要自定義動畫,咱們就來須要知道positon這個值的變化區間。從官方給的ViewPager的兩個示例咱們知道,position的變換有三個區間,[-Infinity,-1),[-1,1],(1.Infinity)。
[-Infinity,-1):已經在屏幕以外,看不到了
(1.Infinity): 已經在屏幕以外,看不到了。
[-1,1]: 這個區間是我門操做View動畫的重點區間。
好比:從A頁面滑動到B頁面,A 頁面的位置變化爲:0 ->-1,B頁面的的位置變化爲:0-> 1 。
瞭解了這個方法的變化後,咱們就來自定義咱們的切換動畫,這裏很簡單,咱們只須要一個scale動畫。代碼以下:
/** * Created by zhouwei on 17/5/26. */ public class CustomTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.9F; @Override public void transformPage(View page, float position) { if(position < -1){ page.setScaleY(MIN_SCALE); }else if(position<= 1){ // float scale = Math.max(MIN_SCALE,1 - Math.abs(position)); page.setScaleY(scale); /*page.setScaleX(scale); if(position<0){ page.setTranslationX(width * (1 - scale) /2); }else{ page.setTranslationX(-width * (1 - scale) /2); }*/ }else{ page.setScaleY(MIN_SCALE); } } }
效果圖是這樣的:
仿魅族Banner效果圖.png
到此,咱們仿魅族Banner的靜態效果就實現了。接下來咱們就要讓Banner動起來,實現無限輪播效果。
上面咱們已經實現了Bannerd的靜態展現和切換動畫,那麼咱們如今就須要讓Banner動起來,實現無限輪播。
ViewPager實現Banner無效輪播效果有2種方案,第一種是:在列表的最前面插入最後一條數據,在列表末尾插入第一個數據,形成循環的假象。第二種方案是:採用getCount 返回 Integer.MAX_VALUE。結下來分別看一下這兩種方案。
1 . 在列表的最前面插入最後一條數據,在列表末尾插入第一個數據,形成循環的假象。
這種方法是怎麼作的呢?,是這樣的:假如咱們的列表有3條數據,用三個頁面展現,分別編號爲1,2,3。咱們再建立一個新的列表,長度爲真實列表的長度+2,也就是5。在最前面插入最後一條數據,而後在末尾插入第一條數據。新列表就變成了這樣了,3-1-2-3-1。若是當前滑到的是0位置(頁面3),那就經過ViewPager的setCurrentItem(int item, boolean smoothScroll)
方法神不知鬼不覺的切換到3位置(頁面3),當滑到4的位置時(頁面1),也用這個方法滑到1位置(頁面1)。這樣給咱們的感受就是無限輪播了。來一張圖輔助理解一下。
輪播切換示意圖.png
2 . 採用getCount 返回 Integer.MAX_VALUE
讓ViewPager 的Adapter getCount 方法返回一個很大的數(這裏用Integer.MAX_VALUE),理論上能夠無限滑動。當顯示完一個真實列表的週期後,又從真實列表的0位置顯示數據,形成無限循環輪播的假象。開始時調用 mViewPager.setCurrentItem(Integer.MAX_VALUE /2)設置選中中間位置,這樣最開始就能夠向左滑動。關鍵代碼:
int currentItem = getStartSelectItem(); //設置當前選中的Item mViewPager.setCurrentItem(currentItem); private int getStartSelectItem(){ // 咱們設置當前選中的位置爲Integer.MAX_VALUE / 2,這樣開始就能往左滑動 // 可是要保證這個值與getRealPosition 的 餘數爲0,由於要從第一頁開始顯示 int currentItem = Integer.MAX_VALUE / 2; if(currentItem % getRealCount() ==0 ){ return currentItem; } // 直到找到從0開始的位置 while (currentItem % getRealCount() == 0){ currentItem++; } return currentItem; }
3 . 兩種方案選哪種?
兩種方案我都試了一下,均可以實現輪播,可是第一種 方案在有切換動畫的時候是有問題的,由於上面咱們說了滑動到最後一頁切換到第一頁時,用的是ViewPager的setCurrentItem(int item, boolean smoothScroll)
方法,smoothScroll 的值爲false,這樣界面就感受不到咱們偷偷的切換。可是這樣切換就沒有了動畫。這樣每次切換就會很生硬,所以就拋棄這種方法。選擇第二種方案。
輪播咱們採用Hanlder的postDelayed方法,關鍵代碼以下:
private final Runnable mLoopRunnable = new Runnable() { @Override public void run() { if(mIsAutoPlay){ mCurrentItem = mViewPager.getCurrentItem(); mCurrentItem++; if(mCurrentItem == mAdapter.getCount() - 1){ mCurrentItem = 0; mViewPager.setCurrentItem(mCurrentItem,false); mHandler.postDelayed(this,mDelayedTime); }else{ mViewPager.setCurrentItem(mCurrentItem); mHandler.postDelayed(this,mDelayedTime); } }else{ mHandler.postDelayed(this,mDelayedTime); } } };
在Adapter instantiateItem(ViewGroup container, final int position) 中,如今的這個position是一個很大的數字,咱們須要將它轉換成一個真實的position,不然會越界報錯。
final int realPosition = position % getRealCount();
/** * 獲取真實的Count * @return */ private int getRealCount(){ return mDatas==null ? 0:mDatas.size(); }
經過以上就實現了仿魅族的BannerView,可是這還沒完,雖然功能實現了,要想在任何地方拿來就可使用,簡單方便,咱們還須要進一步的封裝。
經過上面幾步就能夠實現仿魅族的BannerView,可是爲了使用方便,咱們將它封裝成一個庫,前面一篇文章講了,如何封裝一個通用的ViewPager(文章地址:ViewPager系列之 打造一個通用的ViewPager)。既然要想Banner使用方便,咱們也須要封裝得通用,可擴展。由於咱們的Banner也是用ViewPager 實現的,所以,咱們可用上一篇文章的方法,封裝一個通用的BannerView。
MZBannerView 有如下功能:
1 . 仿魅族BannerView 效果。
2 . 當普通Banner 使用
3 . 當普通ViewPager 使用。
4 . 當普通ViewPager使用(有魅族Banner效果)
自定義屬性
屬性名 | 屬性意義 | 取值 |
---|---|---|
open_mz_mode | 是否開啓魅族模式 | true 爲魅族Banner效果,false 則普通Banner效果 |
canLoop | 是否輪播 | true 輪播,false 則爲普通ViewPager |
indicatorPaddingLeft | 設置指示器距離左側的距離 | 單位爲 dp 的值 |
indicatorPaddingRight | 設置指示器距離右側的距離 | 單位爲 dp 的值 |
indicatorAlign | 設置指示器的位置 | 有三個取值:left 左邊,center 劇中顯示,right 右側顯示 |
經過open_mz_mode
和canLoop
這兩個屬性來控制MZBannerView 是用做Banner仍是普通ViewPager,有4種組合方式
1,仿魅族BannerView(默認的模式)
app:open_mz_mode="true" app:canLoop="true"
2, 普通BannerView
app:open_mz_mode="false" app:canLoop="true"
3 ,普通ViewPager (有魅族Banner的切換動畫)
app:open_mz_mode="true" app:canLoop="false"
4, 普通ViewPager
app:open_mz_mode="false" app:canLoop="false"
使用方法:
1 . xml 佈局文件
<com.zhouwei.mzbanner.MZBannerView
android:id="@+id/banner" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginTop="10dp" app:open_mz_mode="true" app:canLoop="true" app:indicatorAlign="center" app:indicatorPaddingLeft="10dp" />
2 . activity中代碼:
mMZBanner = (MZBannerView) view.findViewById(R.id.banner);
// 設置頁面點擊事件 mMZBanner.setBannerPageClickListener(new MZBannerView.BannerPageClickListener() { @Override public void onPageClick(View view, int position) { Toast.makeText(getContext(),"click page:"+position,Toast.LENGTH_LONG).show(); } }); List<Integer> list = new ArrayList<>(); for(int i=0;i<RES.length;i++){ list.add(RES[i]); } // 設置數據 mMZBanner.setPages(list, new MZHolderCreator<BannerViewHolder>() { @Override public BannerViewHolder createViewHolder() { return new BannerViewHolder(); } }); public static class BannerViewHolder implements MZViewHolder<Integer> { private ImageView mImageView; @Override public View createView(Context context) { // 返回頁面佈局文件 View view = LayoutInflater.from(context).inflate(R.layout.banner_item,null); mImageView = (ImageView) view.findViewById(R.id.banner_image); return view; } @Override public void onBind(Context context, int position, Integer data) { // 數據綁定 mImageView.setImageResource(data); } }
3 .若是是當Banner使用,注意在onResume 中調用start()方法,在onPause中調用 pause() 方法。若是當普通ViewPager使用,則不須要。
@Override public void onPause() { super.onPause(); mMZBanner.pause();//暫停輪播 } @Override public void onResume() { super.onResume(); mMZBanner.start();//開始輪播 }
其餘對外API
/******************************************************************************************************/ /** 對外API **/ /******************************************************************************************************/ //開始輪播 start() //中止輪播 pause() //設置BannerView 的切換時間間隔 setDelayedTime(int delayedTime) // 設置頁面改變監聽器 addPageChangeLisnter(ViewPager.OnPageChangeListener onPageChangeListener) //添加Page點擊事件 setBannerPageClickListener(BannerPageClickListener bannerPageClickListener) //設置是否顯示Indicator setIndicatorVisible(boolean visible) // 獲取ViewPager ViewPager getViewPager() // 設置 Indicator資源 setIndicatorRes(int unSelectRes,int selectRes) //設置頁面數據 setPages(List<T> datas,MZHolderCreator mzHolderCreator) //設置指示器顯示位置 setIndicatorAlign(IndicatorAlign indicatorAlign) //設置ViewPager(Banner)切換速度 setDuration(int duration)
由於是對ViewPager的包裝,全部要設置某些ViewPager的屬性,能夠經過getViewPager 獲取到ViewPager再設置對應屬性
效果圖:
1, BannerView 輪播效果圖:
仿魅族Banner效果.gif
2 普通ViewPager效果圖:
MZBanner普通ViewPager效果.gif
本文講了如何實現一個仿魅族Banner效果。其中講了一些關鍵的點和關鍵代碼。其實普通的BannerView 是同樣的,只是少了動畫而已。最後,將這些功能封裝成了一個通用的BannerView 控件。這個控件既有仿魅族Banner的效果,又能夠當普通Banner使用。並且還能夠看成一個普通的ViewPager使用。
更多詳細代碼和使用方法請看github:https://github.com/pinguo-zhouwei/MZBannerView