这是前一篇文章绘制的CarouselView的升级版,有兴趣的同学,可以去关注一下笔者前一篇文章。自定义广告图片轮播View——CarouselView
这次升级版相对于前文而已,有了一个功能上的飞跃主要区别体现如下:
- 构建方式更简单
- 提供更多的API拱用户自定义
- 提供5中不同应用场景的动画
本文除了提供源码和API外,还会详细讲解如何利用补间动画以及ViewPager.PageTransformer实现花样轮播控件。如果对补间动画还存在疑惑的同学可以阅读笔者的另外一篇文章弥补一下。这次彻底搞懂Android补间动画
先看效果:
饿,不知道为什么,这里做成gif后异常的卡顿,实际效果肯定是如丝版顺滑的。





CarouselView 2.0 如何使用:
ArrayList<CarouselView.CarouselBean> beans=new ArrayList<>();
beans.add(new CarouselView.CarouselBean(R.mipmap.img1,"第一张图"));
beans.add(new CarouselView.CarouselBean(R.mipmap.img2));
beans.add(new CarouselView.CarouselBean(R.mipmap.img3,"第三张图"));
beans.add(new CarouselView.CarouselBean(R.mipmap.img4,"第四张图"));
beans.add(new CarouselView.CarouselBean(R.mipmap.img5,"第五张图"));
beans.add(new CarouselView.CarouselBean(R.mipmap.img6));
carouselView.init(beans,CarouselView.ANIM_LEFTLEAVE);
可见,使用方式还是非常简单的,简单的bean文件,支持传入图片和图片描述(可选),然后调用init方法初始化即可,第二个参数是动画,动画也是选传项,不设置动画则显示传统的轮播控件。
已经封装好5个动画提供给使用者使用。动画支持自定义。
在写文过程中,笔者发现,2.0页不是那么完善,比如没有提供网络图片的显示API,没有对外提供动画自定义的支持等等,这些内容笔者会考虑在3.0里提供,如果收到反馈和留言的话!!
CarouselView API:
void init(ArrayList<CarouselBean> beans,int anim)
: 控件初始化方法
参数一(必填):beans是图片的集合,包含图片和图片描述 。
参数二(选填):anim是动画参数,控件已封装了5个动画,对应5个常量,以ANIM开头。
void setIsRunningCarousel(boolean isRunning)
:控制轮播是否开启,默认为false,即不开启
void setIntervalsTime(int intervalsTime)
:控制轮播间隔时间,默认为5000ms
void setBottomViewBackGroundColor(int colorResource)
:设置底部描述文字布局的背景颜色,推荐#6000
void setPointVisible(boolean visible)
设置是否显示底部指示器,默认为true,即显示
public void setDescVisible(boolean visible)
:是否显示文字描述,默认为false,即不显示
关于轮播动画:
从上面的效果图中,我们可以看到,实现的动画可以分为两大类。
第一类是只显示一个Item的动画,这类动画和传统的轮播没什么大的差异,只是多了一些补间动画修饰。
第二类是一页显示多个Item的动画,这样的轮播图看着更时尚也更高端,相比较于第一类,只是多了这样一段代码:
/**
* 当需要ViewPager一个界面显示多个Item的时候,调用改方法。
*/
private void excisionPage(){
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(120,40,120,40);
viewPager.setLayoutParams(lp);
viewPager.setClipChildren(false);
frameLayout.setClipChildren(false);
//设置Page间间距
viewPager.setPageMargin(20);
//设置缓存的页面数量
if(beans.size()>5)viewPager.setOffscreenPageLimit(2);
}
上图中的viewpager
是包裹在frameLayout
里面的,我们给frameLayout
添加margins
,目的是给两遍的Item预留出显示的地方,然后设置viewpager
的pagermargin
,目的是将3个Item分开,item与item之间留有空白。最后也是最关键的为fragmeLayout
和veiwPager
设置属性setClipChildren(false)
,该属性能够让Android不去自动裁剪超出布局的部分,也就是我们ViewPager
左右两遍的Item正常情况下是会被裁剪掉,不显示的,而设置该属性为false
后能保证他们存活。
添加动画:
既然要添加动画,首先我们肯定需要拿到在滑动的时候的一个可变化的值。ViewPager为我们提供了这样一个借口,我们实现VeiwPager.PageTransformer借口,然后添加进我们的ViewPager即可
private void initAnim(int anim) {
switch (anim){
case ANIM_ALPHA_PAGETRANS:
excisionPage();
viewPager.setPageTransformer(true,new AlphaPageTransformer());
break;
case ANIM_SCALEMAGIC:
excisionPage();
viewPager.setPageTransformer(true,new ScaleMagic());
break;
case ANIM_ROTATEMAGIC:
excisionPage();
viewPager.setPageTransformer(true,new RotateMagic());
break;
case ANIM_LEFTLEAVE:
viewPager.setPageTransformer(true,new LeftLeave());
break;
case ANIM_SCALERIGHTLEAVE:
viewPager.setPageTransformer(true,new ScaleRightLeave());
break;
case ANIM_NORMAL:
default:
viewPager.setClipChildren(true);
frameLayout.setClipChildren(true);
break;
}
}
/**
* 当需要ViewPager一个界面显示多个Item的时候,调用改方法。
*/
private void excisionPage(){
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(120,40,120,40);
viewPager.setLayoutParams(lp);
viewPager.setClipChildren(false);
frameLayout.setClipChildren(false);
//设置Page间间距
viewPager.setPageMargin(20);
//设置缓存的页面数量
if(beans.size()>5)viewPager.setOffscreenPageLimit(2);
}
如何自定义ViewPager的PageTransformer:

我们可以看到
void transformPage(View view, float position)
的position
的值主要需要关注得是position=-1、position=0、postion=1
三个状态,当ViewPager的Item处于正中间时,他的postion=1
。从而我们只要确定3个点的状态,然后添加补间动画让他动起来的就可以了,不过我们也需要绘制在
position<-1,postion>1
的状态,否则在未滑动的时候,会空白。综上所述,我们代码可以这样写:
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
} else if (position <= 0) { // [-1,0]
} else if (position <= 1) { // (0,1]
} else { // (1,+Infinity]
// This page is way off-screen to the right.
}
}
拿两个动画来举个例子:
-
ANIM_SCALEMAGIC:
ANIM_SCALEMAGIC.gif
这个动画是非常实用的,我们看动画可以总结一下几点:
- 一个页面需要显示多个Item
- position=-1时(在左边时),是被缩小了的,有一个Scale动画,还有一个Alpha的渐变动画。postion=1(右边)时,同postion=-1;
有了上述总结,我们直接上代码:
private class ScaleMagic implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.90f;
private static final float MIN_ALPHA = 0.5f;
@Override
public void transformPage(View page, float position) {
if (position < -1 || position > 1) {
page.setAlpha(MIN_ALPHA);
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
} else if (position <= 1) { // [-1,1]
if (position < 0) {
float scaleX = 1 + 0.1f * position;
page.setScaleX(scaleX);
page.setScaleY(scaleX);
} else {
float scaleX = 1 - 0.1f * position;
page.setScaleX(scaleX);
page.setScaleY(scaleX);
}
float scaleFactor = Math.max(MIN_ALPHA, 1 - Math.abs(position));
page.setAlpha(MIN_ALPHA + (scaleFactor - MIN_ALPHA) / (1 - MIN_ALPHA) * (1 - MIN_ALPHA));
}
}
}
-
我们设置两个常量,默认在左右边的渐变和缩放大小分别为50%和90%
-
设置(-infinite,-1)和(1,infinite) 的状态,即在左右两遍时,view十倍缩小了并且透明了
-
设置[-1,0)的状态,当postion=-1时,我们需要ScaleX=0.9f,postion=0时,ScaleX=1.0f。很显然,这只是一个二元一次方程,可得ScaleX=1+.01f*position。
-
设置(0,1]的状态,同上方式,我们可以计算出ScaleX=1-0.1f*position。
-
到此我们已经完成了Scale动画的绘制,然后是Aplha动画,从[-1,1]Item是一个从透明到不透明再到透明的过程,笔者采用了代码中的算法,其实可以同设置Scale动画一样,逐步设置。
-
ANIM_SCALERIGHTLEAVE:
ANIM_SCALERIGHTLEAVE.gif
首先先总结一下这个动画的要点:
- 传统的轮播方式,同一页只显示一个Item
- 滑动时包含一个缩放动画
- 缩小时包含一个渐变动画
- 设置两个缩放和渐变的最小渐变值分别为0.85f和0.5f
- (-infinity,-1)(-,infinity)设置为全透明,因为一个页面值显示Item,所以为了方便,直接可以设置全透明
- (-1,1)分别设置位移动画、缩放动画以及渐变动画
源码分享:
package com.wusy.adv;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
/**
* Created by XIAO RONG on 2018/8/8.
*/
public class CarouselView extends LinearLayout implements ViewPager.OnPageChangeListener {
public static final int ANIM_NORMAL=0;
public static final int ANIM_ALPHA_PAGETRANS=1;
public static final int ANIM_SCALEMAGIC=2;
public static final int ANIM_ROTATEMAGIC=3;
public static final int ANIM_LEFTLEAVE=4;
public static final int ANIM_SCALERIGHTLEAVE=5;
private FrameLayout frameLayout;
private LinearLayout ll_bottomview;
private String TAG="CarouselView";
private Context mC;
private ViewPager viewPager;
private LinearLayout ll_point;
private TextView tv_desc;
private ArrayList<ImageView> imageViews; //存放图片的集合
private ArrayList<CarouselBean> beans;
private int lastPosition;
private boolean isRunning = false;
private int intervalsTime=5000;
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
viewPager.setCurrentItem(viewPager.getCurrentItem()+1);
break;
}
}
};
public CarouselView(Context context) {
this(context,null);
}
public CarouselView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public CarouselView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.view_carousel, this);
this.mC=context;
findView();
}
private void findView(){
viewPager=findViewById(R.id.view_carousel_viewpager);
tv_desc=findViewById(R.id.view_carousel_tv_desc);
ll_point=findViewById(R.id.view_carousel_ll_point);
frameLayout=findViewById(R.id.view_carousel_framelayout);
ll_bottomview=findViewById(R.id.view_carousel_ll_bottomview);
}
public void init(ArrayList<CarouselBean> beans,int anim){
this.beans=beans;
initView();
initAnim(anim);
initAdapter();
startCarousel();
}
public void init(ArrayList<CarouselBean> beans){
init(beans,ANIM_NORMAL);
}
/**
* 是否开启轮播,默认不开启
* @param isRunning
*/
public void setIsRunningCarousel(boolean isRunning){
this.isRunning=isRunning;
}
/**
* 轮播间隔时间,默认为5000毫秒
* @param intervalsTime
*/
public void setIntervalsTime(int intervalsTime){
this.intervalsTime=intervalsTime;
}
/**
* 设置底部描述布局的背景颜色。推荐#6000
* @param colorResource
*/
public void setBottomViewBackGroundColor(int colorResource){
ll_bottomview.setBackgroundColor(colorResource);
}
/**
* 设置是否显示指示器,默认显示
* @param visible
*/
public void setPointVisible(boolean visible){
if (visible) ll_point.setVisibility(VISIBLE);
else ll_point.setVisibility(GONE);
}
/**
* 设置是否显示文字描述。默认不显示
* @param visible
*/
public void setDescVisible(boolean visible){
if (visible) tv_desc.setVisibility(VISIBLE);
else tv_desc.setVisibility(GONE);
}
private void initAnim(int anim) {
switch (anim){
case ANIM_ALPHA_PAGETRANS:
excisionPage();
viewPager.setPageTransformer(true,new AlphaPageTransformer());
break;
case ANIM_SCALEMAGIC:
excisionPage();
viewPager.setPageTransformer(true,new ScaleMagic());
break;
case ANIM_ROTATEMAGIC:
excisionPage();
viewPager.setPageTransformer(true,new RotateMagic());
break;
case ANIM_LEFTLEAVE:
viewPager.setPageTransformer(true,new LeftLeave());
break;
case ANIM_SCALERIGHTLEAVE:
viewPager.setPageTransformer(true,new ScaleRightLeave());
break;
case ANIM_NORMAL:
default:
viewPager.setClipChildren(true);
frameLayout.setClipChildren(true);
break;
}
}
/**
* 当需要ViewPager一个界面显示多个Item的时候,调用改方法。
*/
private void excisionPage(){
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(120,40,120,40);
viewPager.setLayoutParams(lp);
viewPager.setClipChildren(false);
frameLayout.setClipChildren(false);
//设置Page间间距
viewPager.setPageMargin(20);
//设置缓存的页面数量
if(beans.size()>5)viewPager.setOffscreenPageLimit(2);
}
/**
* 初始化适配器
*/
private void initAdapter() {
ll_point.getChildAt(0).setEnabled(true);//初始化控件时,设置第一个小圆点为亮色
tv_desc.setText(beans.get(0).getImgDescs()); //设置第一个图片对应的文字
lastPosition=0;
viewPager.setAdapter(new CarouseAdapter());
Log.i(TAG,"initAdapter()完成");
}
/**
* 初始化控件
*/
private void initView(){
viewPager.setOnPageChangeListener(this);
imageViews=new ArrayList<>();
ImageView imageView;
View pointView;
for (int i = 0; i < beans.size(); i++){
//添加图片到集合中
imageView = new ImageView(mC);
imageView.setBackgroundResource(beans.get(i).getImgResource());
imageViews.add(imageView);
//加小白点,指示器(这里的小圆点定义在了drawable下的选择器中了,也可以用小图片代替)
pointView = new View(mC);
pointView.setBackgroundResource(R.drawable.carousel_point); //使用选择器设置背景
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(8, 8);
if (i != 0){
//如果不是第一个点,则设置点的左边距
layoutParams.leftMargin = 10;
}
pointView.setEnabled(false); //默认都是暗色的
ll_point.addView(pointView, layoutParams);
}
if(imageViews.size()>0) Log.i(TAG,"initView完成");
}
/**
* 开启轮播
*/
private void startCarousel(){
Log.i(TAG,"CarouselView轮播开启");
new Thread(){
@Override
public void run() {
while(isRunning){
try {
Thread.sleep(intervalsTime);
handler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
//页面滑动
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
//新的页面被选中
@Override
public void onPageSelected(int position) {
//当前的位置可能很大,为了防止下标越界,对要显示的图片的总数进行取余
int newPosition = position % beans.size();
//设置描述信息
if (beans.get(newPosition).getImgDescs()!=null)tv_desc.setText(beans.get(newPosition).getImgDescs());
else tv_desc.setText("");
//设置小圆点为高亮或暗色
ll_point.getChildAt(lastPosition).setEnabled(false);
ll_point.getChildAt(newPosition).setEnabled(true);
lastPosition = newPosition; //记录之前的点
}
//页面滑动状态发生改变
@Override
public void onPageScrollStateChanged(int state) {
}
private class CarouseAdapter extends PagerAdapter{
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view==object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.i(TAG,"执行,position="+position);
//从集合中获得图片
int newPosition = position % imageViews.size(); //数组中总共有5张图片,超过数组长度时,取摸,防止下标越界
ImageView imageView = imageViews.get(newPosition);
//把图片添加到container中
container.addView(imageView);
//把图片返回给框架,用来缓存
return imageView;
}
//销毁条目
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
//object:刚才创建的对象,即要销毁的对象
container.removeView((View) object);
}
}
public static class CarouselBean{
private int imgResource;
private String imgDescs;
public int getImgResource() {
return imgResource;
}
public void setImgResource(int imgResource) {
this.imgResource = imgResource;
}
public String getImgDescs() {
return imgDescs;
}
public void setImgDescs(String imgDescs) {
this.imgDescs = imgDescs;
}
public CarouselBean(int imgResource, String imgDescs) {
this.imgResource = imgResource;
this.imgDescs = imgDescs;
}
public CarouselBean(int imgResource) {
this.imgResource = imgResource;
}
}
/**
* 单纯渐变动画
*/
private class AlphaPageTransformer implements ViewPager.PageTransformer{
private float mMinAlpha = 0.5f;
@Override
public void transformPage(View view, float position) {
float factor;
if (position < -1) {
view.setAlpha(mMinAlpha);
} else if (position <= 1) { // [-1,1]
if (position <= 0){ //[-1,0)
factor = mMinAlpha + (1 - mMinAlpha) * (1 + position);
view.setAlpha(factor);
} else{//[0,1]
factor = mMinAlpha + (1 - mMinAlpha) * (1 - position);
view.setAlpha(factor);
}
}else { // (1,+Infinity]
view.setAlpha(mMinAlpha);
}
}
}
/**
* 渐变+缩放动画
*/
private class ScaleMagic implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.90f;
private static final float MIN_ALPHA = 0.5f;
@Override
public void transformPage(View page, float position) {
if (position < -1 || position > 1) {
page.setAlpha(MIN_ALPHA);
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
} else if (position <= 1) { // [-1,1]
if (position < 0) {
float scaleX = 1 + 0.1f * position;
page.setScaleX(scaleX);
page.setScaleY(scaleX);
} else {
float scaleX = 1 - 0.1f * position;
page.setScaleX(scaleX);
page.setScaleY(scaleX);
}
float scaleFactor = Math.max(MIN_ALPHA, 1 - Math.abs(position));
page.setAlpha(MIN_ALPHA + (scaleFactor - MIN_ALPHA) / (1 - MIN_ALPHA) * (1 - MIN_ALPHA));
}
}
}
/**
* 旋转+渐变
*/
private class RotateMagic implements ViewPager.PageTransformer {
private float mMaxRotate = 15.0f;
private float MIN_ALPHA = 0.7f;
@Override
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
view.setRotation(mMaxRotate * -1);
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight());
} else if (position <= 1) { // [-1,1]
if (position <= 0){ //[0,-1]
view.setPivotX(view.getWidth() * (0.5f + 0.5f * (-position)));
view.setPivotY(view.getHeight());
view.setRotation(mMaxRotate * position);
} else{//[1,0]
view.setPivotX(view.getWidth() * 0.5f * (1 - position));
view.setPivotY(view.getHeight());
view.setRotation(mMaxRotate * position);
}
float scaleFactor = Math.max(MIN_ALPHA, 1 - Math.abs(position));
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_ALPHA) / (1 - MIN_ALPHA) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
view.setRotation(mMaxRotate);
view.setPivotX(view.getWidth() * 0);
view.setPivotY(view.getHeight());
}
}
}
/**
* 左边离开
*/
private class LeftLeave implements ViewPager.PageTransformer {
private final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
/**
* 缩小右边离开
*/
private class ScaleRightLeave implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
Log.e("TAG", view + " , " + position + "");
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0
{ // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
/ (1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
}
网友评论