ViewPager无限轮播与自定义切换动画
一直在寻求一个能用得长久的ViewPager,寻寻觅觅终于发现,ViewPager有这一个就够了。
注:并非完全原创
先看一下效果:
淡入淡出:
旋转:
无限轮播的ViewPager
主要设计思路(以ViewPager右滑为例):
- 无限轮播:通过Handler负责View的切换,每次向自身发送延迟消息,达到无限循环的目的;当处于ViewPager最后一个时候,继续向右滑,调用setCurrentItem,回到第一个。
- 其中很关键的点,就是最后一个Item回到第一个的时候,默认动画太难看了,因此,需要将首尾两个Item设置成相同的,在首尾切换的时候,将动画暂时取消掉,以用户不易察觉的情况,偷偷切回第一个Item。
- 自动切换动画的速度设置:通过Java反射机制获取父类的Scroller参数,在子类中创建我们自己的Scroller,然后再透过反射,将我们自己的Scroll塞给父类,这样就可以通过Scroller设置切换动画的速度。
/**
* 自动轮播的ViewPager
* Created by ChenSS on 2017/1/5.
*/
public class MylViewPager extends ViewPager {
public static final int DEFAULT_INTERVAL = 1500;
public static final int LEFT = 0;
public static final int RIGHT = 1;
/**
* 什么也不做,当滑动在最后一项或者第一项,什么也不做
**/
public static final int SLIDE_BORDER_MODE_NONE = 0;
/**
* 当滑动在最后一项或者第一项,开启循环,这种模式下,当滑到最后一个Item回到第一个Item,
* 这种模式下,最后一个Item和第一个Item相同的话会显得自然一些
**/
public static final int SLIDE_BORDER_MODE_CYCLE = 1;
/**
* 当滑动在最后一项或者第一项,开启循环,并且阻止事件拦截
**/
public static final int SLIDE_BORDER_MODE_TO_PARENT = 2;
/**
* 自动滚动的时间,以毫秒为单位
**/
private long interval = DEFAULT_INTERVAL;
/**
* 自动滚动方向, default is {@link #RIGHT}
**/
private int direction = RIGHT;
/**
* 是否自动循环
**/
private boolean isCycle = true;
/**
* 接触时是否停止自动滚动
**/
private boolean stopScrollWhenTouch = true;
/**
* 如何处理当滑动在最后还是第一项
**/
private int slideBorderMode = SLIDE_BORDER_MODE_NONE;
/**
* 动画时是否自动滚动在最后或第一项取消动画
**/
private boolean isBorderAnimation = false;
private Handler handler;
private boolean isAutoScroll = false;
private boolean isStopByTouch = false;
private float touchX = 0f, downX = 0f;
private CustomDurationScroller scroller = null;
public static final int SCROLL_WHAT = 0;
public MylViewPager(Context paramContext) {
super(paramContext);
init();
}
public MylViewPager(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
init();
}
private void init() {
handler = new MyHandler();
setViewPagerScroller();
}
public void setSlideBorderMode(int slideBorderMode) {
this.slideBorderMode = slideBorderMode;
}
public void setDirection(int direction) {
this.direction = direction;
}
/**
* 启动动画
*/
public void startAutoScroll() {
isAutoScroll = true;
sendScrollMessage(interval);
}
/**
* 启动动画
*/
public void startAutoScroll(int delayTimeInMills) {
isAutoScroll = true;
sendScrollMessage(delayTimeInMills);
}
/**
* 停止动画
*/
public void stopAutoScroll() {
isAutoScroll = false;
handler.removeMessages(SCROLL_WHAT);
}
/**
* 设置动画切换的时间
*/
public void setScrollDurationFactor(double scrollFactor) {
scroller.setScrollDurationFactor(scrollFactor);
}
private void sendScrollMessage(long delayTimeInMills) {
handler.removeMessages(SCROLL_WHAT);
handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
}
/**
* 利用反射机制修改滚动的时间
*/
private void setViewPagerScroller() {
try {
Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
scrollerField.setAccessible(true);
//插值器
Field interpolatorField = ViewPager.class.getDeclaredField("sInterpolator");
interpolatorField.setAccessible(true);
scroller = new CustomDurationScroller(getContext(), (Interpolator) interpolatorField.get(null));
scrollerField.set(this, scroller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 选择滑动到哪一页
*/
public void scrollOnce() {
PagerAdapter adapter = getAdapter();
int currentItem = getCurrentItem();
int totalCount;
if (adapter == null || (totalCount = adapter.getCount()) <= 1) {
return;
}
int nextItem = (direction == LEFT) ? --currentItem : ++currentItem;
if (nextItem < 0) {
if (isCycle) {
setCurrentItem(totalCount - 1, isBorderAnimation);
//如果是第一个就0延迟
sendScrollMessage(0);
}
} else if (nextItem == totalCount) {
if (isCycle) {
setCurrentItem(0, isBorderAnimation);
//如果是最后一个就0延迟
sendScrollMessage(0);
}
} else {
//默认情况就按照设定的时间切换图片
setCurrentItem(nextItem, true);
sendScrollMessage(interval);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (stopScrollWhenTouch) {
if (ev.getAction() == MotionEvent.ACTION_DOWN && isAutoScroll) {
isStopByTouch = true;
stopAutoScroll();
} else if (ev.getAction() == MotionEvent.ACTION_UP && isStopByTouch) {
startAutoScroll();
}
}
if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT || slideBorderMode == SLIDE_BORDER_MODE_CYCLE) {
touchX = ev.getX();
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
downX = touchX;
}
int currentItem = getCurrentItem();
PagerAdapter adapter = getAdapter();
int pageCount = adapter == null ? 0 : adapter.getCount();
if ((currentItem == 0 && downX <= touchX) || (currentItem == pageCount - 1 && downX >= touchX)) {
//事件拦截
if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT) {
getParent().requestDisallowInterceptTouchEvent(false);
} else {
if (pageCount > 1) {
setCurrentItem(pageCount - currentItem - 1, isBorderAnimation);
}
getParent().requestDisallowInterceptTouchEvent(true);
}
return super.onTouchEvent(ev);
}
}
getParent().requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(ev);
}
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCROLL_WHAT:
scrollOnce();
//下面的方法,Handler向自身发送延迟消息
//sendScrollMessage(interval);
default:
break;
}
}
}
class CustomDurationScroller extends Scroller {
private double scrollFactor = 1;
public CustomDurationScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
/**
* 设置滚动切换的时间
*/
public void setScrollDurationFactor(double scrollFactor) {
this.scrollFactor = scrollFactor;
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * scrollFactor));
}
}
}
自定义ViewPager动画
首先你要实现ViewPager.PageTransformer接口,你需要设计transformPage(View view, float position)的方法体,第一个参数是具体的Item,第二个参数是Item的position,也就是Item的位置。
position是一个极为关键的参数, 当我们的View显示在屏幕上的时候,position的值是-1到1。
- 当position大于-1小于0时,表示View正在从左侧滑入(也可能滑出,如果ViewPager滑的方向换掉);
- 当等于0,表示View刚刚好显示在屏幕中,但是通常获取不到这个刚刚等零的值;
- 当position大于0小于1,表示View正在从右侧滑出(也可能滑入,如果ViewPager滑的方向换掉)。
淡入淡出的ViewPager切换动画
/**
* ViewPager的滑动特效
* Created by ChenSS on 2017/1/5.
*/
public class AlphaPageTransformer implements ViewPager.PageTransformer {
/**
* 以阿尔法值为例,这个是0-1的值,可以设置View的透明度
*/
private float alpha;
/**
* 在这里编辑你ViewPager的动画特效
*
* @param view 拿到你ViewPager的Item的View
* @param position View现在所处的位置
*/
@Override
public void transformPage(View view, float position) {
if (position < -1) {
//当它消失在左边,通常还原最普通的状态,设置Alpha为1
ViewHelper.setAlpha(view, 1);
} else if (position <= 0) {
//左滑特效,这个时候它的position在屏幕左侧,所以是负值
alpha = 1 - (0 - position);
ViewHelper.setAlpha(view, alpha);
} else if (position <= 1) {
//右滑特效,这个时候它的position在屏幕上,所以是正值
alpha = 1 - position;
ViewHelper.setAlpha(view, alpha);
} else {
//当它消失在右边,通常还原最普通的状态,设置Alpha为1
ViewHelper.setAlpha(view, 1);
}
}
}
旋转的ViewPager切换动画
/**
* 旋转
* Created by ChenSS on 2017/1/5.
*/
public class RotatePageTransformer implements ViewPager.PageTransformer {
private static final float BASE_ANGLE = 10.0f;
private float angle;
public void transformPage(View view, float position) {
if (position < -1) {
ViewHelper.setRotation(view, 0);
} else if (position <= 0) {
angle = (BASE_ANGLE * position);
ViewHelper.setPivotX(view, view.getMeasuredWidth() * 0.5f);
ViewHelper.setPivotY(view, view.getMeasuredWidth() * 0.5f);
ViewHelper.setRotation(view, angle);
} else if (position <= 1) {
angle = (BASE_ANGLE * position);
ViewHelper.setPivotX(view, view.getMeasuredWidth() * 0.5f);
ViewHelper.setPivotY(view, view.getMeasuredWidth() * 0.5f);
ViewHelper.setRotation(view, angle);
} else {
ViewHelper.setRotation(view, 0);
}
}
}
测试代码
代码我没全贴出来,Activity的布局文件只有ViewPager,然后Fragment的布局文件只有ImageView,比较简单。
public class MaterialDesignViewPagerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_material_design_view_pager);
MylViewPager viewPager = (MylViewPager) findViewById(R.id.md_view_pager_asvp);
ArrayList<Fragment> fragmentList = new ArrayList<>();
//注意首尾的Item是一样的
fragmentList.add(new RecyclerViewBean(R.mipmap.b_1));
fragmentList.add(new RecyclerViewBean(R.mipmap.b_2));
fragmentList.add(new RecyclerViewBean(R.mipmap.b_3));
fragmentList.add(new RecyclerViewBean(R.mipmap.b_1));
RecyclerVpAdapter bAdapter = new RecyclerVpAdapter(getSupportFragmentManager(), fragmentList);
viewPager.setAdapter(bAdapter);
viewPager.setCurrentItem(0);
//自动滑动方向往左
viewPager.setDirection(MylViewPager.LEFT);
//开启无限循环的MODE
viewPager.setSlideBorderMode(MylViewPager.SLIDE_BORDER_MODE_CYCLE);
//设置自定义Alpha切换动画效果
viewPager.setPageTransformer(true, new AlphaPageTransformer());
//设置动画切换的速度
viewPager.setScrollDurationFactor(10);
//开启自动轮播,设置每次切换的时间间隔
viewPager.startAutoScroll(2000);
}
class RecyclerVpAdapter extends FragmentPagerAdapter {
ArrayList<Fragment> list;
public RecyclerVpAdapter(FragmentManager fragmentManager, ArrayList<Fragment> list) {
super(fragmentManager);
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Fragment getItem(int arg0) {
return list.get(arg0);
}
}
}
ViewPager无限轮播与自定义切换动画的更多相关文章
- 踩石行动:ViewPager无限轮播的坑
2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...
- ViewPager +无限轮播+滑动速度修改+指示小点
养成习惯,做过代码记录总结. ViewPager 使用记录 1. ViewPage 位于V4包. 2.主要用来做banner轮播. 3.原理:适配器重用提高效率,与listview等一个原理. 下面记 ...
- ViewPager实现无限轮播踩坑记
最近笔者想通过ViewPager来实现一个广告Banner,并实现无限轮播的效果,但是在这个过程中踩了不少的坑,听我慢慢道来.如果大家有遇到和我一样的情况,可以参考我的解决方法,没有那就更好,如果针对 ...
- Android使用ViewPager做轮播
ViewPager.html div.oembedall-githubrepos { border: 1px solid #DDD; list-style-type: none; margin: 0 ...
- Android真正意义上的无限轮播Banner
在android开发的时候,经常会使用到轮播图,对于这种效果,一般情况下,我们都会使用一种叫做ViewPager的来实现. 传统的实现逻辑是自定义一个View继承ViewPager,在适配器中 将co ...
- Android之仿京东淘宝的自动无限轮播控件
在App的开发中,很多的时候都需要实现类似京东淘宝一样的自动无限轮播的广告栏,所以就自己写了一个,下面是我自定义控件的思路和过程. 一.自定义控件属性 新建自定义控件SliderLayout继承于Re ...
- Android 轮播图Banner切换图片的效果
Android XBanner使用详解 2018年03月14日 08:19:59 AND_Devil 阅读数:910 版权声明:本文为博主原创文章,未经博主允许不得转载. https://www. ...
- iOS:实现图片的无限轮播(二)---之使用第三方库SDCycleScrollView
iOS:实现图片的无限轮播(二)---之使用第三方库SDCycleScrollView 时间:2016-01-19 19:13:43 阅读:630 评论:0 收藏:0 ...
- iOS开发UI篇—无限轮播(功能完善)
iOS开发UI篇—无限轮播(功能完善) 一.自动滚动 添加并设置一个定时器,每个2.0秒,就跳转到下一条. 获取当前正在展示的位置. [self addNSTimer]; } -(void)addNS ...
随机推荐
- leetcode 697. Degree of an Array
题目: Given a non-empty array of non-negative integers nums, the degree of this array is defined as th ...
- django(注册→登录→主页)增强版
首先准备一张空白的数据表: urls展示: views主要的几个函数以及数据库链接代码:↓ 后端编写结束↑ ↓前端 前端界面:↓ 幸好成功了,接下来看看数据库有没有插入数据.... 这么 ...
- Android+ESP8266+路由器实现远程控制(基于花生壳域名方式访问)
x先说一下实现的功能,其实就是远程控制 和这篇文章的控制 http://www.cnblogs.com/yangfengwu/p/5295632.html 应该说是这篇文章的升级,解决这篇文章由 ...
- 使用Aspose.Cells利用模板导出Excel(C#)
前言 随着互联网的流行,web项目逐渐占据主流.我相信大部分人开发项目的过程中都写过上传以及导出Excel和Word的功能,本文仅讨论导出Excel.C#中有很多第三方组件支持导出Excel,比如:N ...
- *.do和*.action的区别
最近发现个问题,都是SpringMVC 请求地址有的是*.do有的是*.action,想了半天区别没想出来. struts早期的1版本,以.do为后缀.同时spring的MVC也是以.do为后缀.几年 ...
- SSM框架+slf4j 以Gradle实现
环境:win10+jdk8+tomcat9+Intellij IDEA 首先,作为一个喜欢偷懒的人,管理jar之类的的事情太累,所以用了Gradle项目管理器 第一步: 新建一个gradle-web项 ...
- LeetCode 189. Rotate Array (旋转数组)
Rotate an array of n elements to the right by k steps. For example, with n = 7 and k = 3, the array ...
- go 代码的调试---打印调用堆栈
本文介绍如何打印调用堆栈进行go代码的调试. 打印堆栈使用的runtime package中的Stack()函数 func Stack(buf []byte, all bool) int Stack ...
- 忘记root密码,进入单用户模式修改密码
进入单用户模式 rhel61.在系统数秒时,按下键,进入到系统引导菜单 中2.选择系统后 按“e”键 选择kernel后 按“e”键 后空格 1+回车 b:启动系统 进入到单用户模式 rhel71.在 ...
- unity客户端基本框架(转载)
框架概述: 基础系统的框架搭建,其中包括: UI框架(NGUI + MVC) 消息管理(Advanced CSharp Messenger) 网络层框架(Socket + Protobuf ) 表格数 ...