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