转载:http://www.open-open.com/lib/view/open1496585426285.html

使用方法:http://www.see-source.com/androidwidget/detail.html?wid=1362

前言

Banner广告位是APP 中的一个非常重要的位置,为什么呢?因为它能带来money。是一个公司很重要的一个营收点。像那种用户数基数特别大的产品,如facebook、twitter、QQ、微信等等。Banner广告位日营收估计得上千万美刀(猜的,不知道具体数据)。一个漂亮的Banner往往能够吸引用户的眼球,引导用户点击,从而提高转化率。遗憾的是现在的大多数产品的Banner都是千篇一律的,没有什么亮点可言。但是前几天在魅族手机上发现了一个效果不错的Banner,魅族所有自家的APP所用的Banner 引起了我的注意。效果是这样子的:

meizuapp.gif

看到这个Banner 第一眼就吸引了我,随后就反复的体验了几次了,感觉这种Banner的效果还不错。最后想着高仿一个和这种效果差不多的BannerView 。那么本文就讲一下如何实现这样一个BannerView。最终实现的效果如下:

MZBannerView.gif

目录

本文会讲实现仿魅族Banner效果所要用到的一些关键知识点,目录如下图所示。所有的效果已经封装成一个库。详细代码请看github: https://github.com/pinguo-zhouwei/MZBannerView

本文目录.png

仿魅族Banner 效果

在开始实现魅族Banner效果之前,我们先来整理一下实现一个BannerView的思路,首先需要用ViewPager,其次让ViewPager无限轮播。其实BannerView就是一个无限轮播的ViewPager,然后做一些封装处理,让使用更加简单就ok。

现在我们在来看一下魅族的这个Banner。他与普通的banner的区别是当前页显示了前一页和后一页的部分内容。

ViewPager展示多页.png

抛开切换时的动画先不说,要实现这个效果的第一步就是要让ViewPager在一个页面显示多页的内容(当前页+前后页部分)。

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,但是这还没完,虽然功能实现了,要想在任何地方拿来就可以使用,简单方便,我们还需要进一步的封装。

封装轮子:MZBannerView

通过上面几步就可以实现仿魅族的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使用。

项目主页:http://www.open-open.com/lib/view/home/1496585426285

ViewPager系列之 仿魅族应用的广告BannerView(转)的更多相关文章

  1. ViewPager系列之 仿魅族应用的广告BannerView

    转自:https://juejin.im/post/5933c65d0ce463005717cbe9 前言 Banner广告位是APP 中的一个非常重要的位置,为什么呢?因为它能带来money.是一个 ...

  2. 安卓开发笔记——Fragment+ViewPager组件(高仿微信界面)

    什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再 ...

  3. 转-Fragment+ViewPager组件(高仿微信界面)

    http://www.cnblogs.com/lichenwei/p/3982302.html 什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开 ...

  4. Android 仿知乎创意广告

    代码地址如下:http://www.demodashi.com/demo/14904.html 一.概述 貌似前段时间刷知乎看到的一种非常有特色的广告展现方式,即在列表页,某一个Item显示背后部分广 ...

  5. 转-ViewPager组件(仿微信引导界面)

    http://www.cnblogs.com/lichenwei/p/3970053.html 这2天事情比较多,都没时间更新博客,趁周末,继续继续~ 今天来讲个比较新潮的组件——ViewPager ...

  6. 撸一个Android高性能日历控件,高仿魅族

    Android原生的CalendarView根本无法满足我们日常开发的需要,在开发吾记APP的过程中,我觉得需要来一款高性能且美观简洁的日历控件,觉得魅族的日历风格十分适合,于是打算撸一款. gith ...

  7. STM32F030系列实现仿位带操作

    1.闲言 最近开发的时候,用到了STM32F030F4P6型号的单片机,它只有20个引脚,价格非常便宜,但是功能齐全:定时器.外部中断.串口.IIC.SPI.DMA和WWDG等等,应用尽有,非常适合用 ...

  8. 安卓开发笔记——ViewPager组件(仿微信引导界面)

    这2天事情比较多,都没时间更新博客,趁周末,继续继续~ 今天来讲个比较新潮的组件——ViewPager 什么是ViewPager? ViewPager是安卓3.0之后提供的新特性,继承自ViewGro ...

  9. Android商城开发系列(十)—— 首页活动广告布局实现

    在上一篇博客当中,我们讲了频道布局的实现,接下来我们讲解一下活动广告布局的实现,效果如下图: 这个是用viewpager去实现的,新建一个act_item.xml,代码如下所示: <?xml v ...

随机推荐

  1. SQL PARTITION BY:列值改变时重置计数

    现有数据如下: 需求:以科目为单位 对分数进行排序 SELECT *, ROW_NUMBER() OVER (PARTITION BY 科目 ORDER BY 分数 DESC) AS NUM FROM ...

  2. 在java代码中设置margin

    我们平常可以直接在xml里设置margin,如: <ImageView android:layout_margin="5dip" android:src="@dra ...

  3. MTD

    内存技术设备(英语:Memory Technology Device,缩写为 MTD),是Linux系统中设备文件系统的一个类别,主要用于快闪存储器的应用,是一种快闪存储器转换层(Flash Tran ...

  4. Using ASIHTTPRequest in an iOS project

    1) Add the files Copy the files you need to your project folder, and add them to your Xcode project. ...

  5. Volley缓存说明——一个请求两次回调

    从上一篇文章Android 异步网络请求框架-Volley了解volley的一些出来过程,当然也包含网络请求和缓存处理的流程,但是在此需要单独做一些说明. 我在使用过程中忽略了一个事情,就是一个网络请 ...

  6. Scala各种符号含义;scala =>符号含义总结

    符号: <- :for循环 -> :map映射 => :匿名函数 Int= :scala函数 _ :通配符 https://blog.csdn.net/bon_mot/article ...

  7. zabbix自动化监控之自动注册

    自动注册与自动发现刚好相反,是zabbix agent主动联系zabbix server,最后由zabbix server将这些agent加到host里.活动的Zabbix agent可以自动注册到服 ...

  8. UVA 111 (复习dp, 14.07.09)

     History Grading  Background Many problems in Computer Science involve maximizing some measure accor ...

  9. OpenSSL Heartbleed "心脏滴血"漏洞简单攻击示例

    转自:http://www.lijiejie.com/openssl-heartbleed-attack/ OpenSSL Heartbleed漏洞的公开和流行让许多人兴奋了一把,也让另一些人惊慌了一 ...

  10. A Quick Look at P3P

    P3P Made Simple By default, IE will reject cookies coming from 3rd-party contexts. A 3rd-party conte ...