Android 自定义 ViewPager 打造千变万化的图片切换效果

小近 发布于 2014/10/30 14:03
阅读 2K+
收藏 25

记得第一次见到ViewPager这个控件,瞬间爱不释手,做东西的主界面通通ViewPager,以及图片切换也抛弃了ImageSwitch之类的,开始让ViewPager来做。时间长了,ViewPager的切换效果觉得枯燥,形成了审美疲劳~~我们需要改变,今天教大家如何改变ViewPager切换时的效果,实现个性化的图片切换~~

看一下这样效果的图片切换:

是不是比传统的效果个性很多,嘿嘿~~其实很简单,学习完这篇博客,保证你可以自定义切换效果,做出各种丧心病狂的切换~~

1、制作前的分析

观察下效果图,实际上改变的就是切换时的动画,那么简单了,只需要用户在切换时,拿到当前的View和下一个View,然后添加动画是不是就可以了。好,第一步,获取用户切换时的当前View和切换至的目的View。

我们在来看一下,如果或者了当前View和目的View,对于动画我们需要缓慢的变化,最好是根据用户的手势滑动。比如上述效果,用户滑动时,目的图片根据用户滑动距离缓缓出现和慢慢变大。好,第二步,设计动画的梯度变化。

经过分析,我们总结出两个步骤,下面我们开始一步一步来打造~~~

2、获取用户切换时当前View和切换至的目的View。

ViewPager也需要监听用户的手势,所以肯定提供了某个方法。于是纵观ViewPager的方法,发现了一个叫做 onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的方法~~

没错就是这个方法:在页面滚动时调用~

下面仔细研究下这几个参数:

直接说测试结果:

在非第一页与最后一页时,滑动到下一页,position为当前页位置;滑动到上一页:position为当前页-1

positionOffset 滑动到下一页,[0,1)区间上变化;滑动到上一页:(1,0]区间上变化

positionOffsetPixels这个和positionOffset很像:滑动到下一页,[0,宽度)区间上变化;滑动到上一页:(宽度,0]区间上变化

第一页时:滑动到上一页position=0 ,其他基本为0 ;最后一页滑动到下一页 position为当前页位置,其他两个参数为0

 

豁然发现,我们需要的步骤的第二步解决了,positionOffset很适合作为,渐变,缩放的控制参数;positionOffsetPixels则可以作为平移等的控制参数。

那么如何获得当前View和目的View呢:

分享几个我的歧途:

1、【错误】我通过getChildAt(position),getChildAt(position+1),getChildAt(position-1)获得滑动时,左右的两个View;乍一看,还真觉得不错~~~在代码写出来,再乍效果也出不来~~错误原因:我们忽略一个特别大的东西,ViewPager的机制,滑动时动态加载和删除View,ViewPager其实只会维持2到3个View,而position的范围基本属于无限~~

2、【错误】我通过getCurrentItem获得当前的位置,然后+1,-1获得后一个或者前一个~~正在窃喜,赶快代码改过来,效果怎么也不对,乱七八糟的~~仔细观察日志,这个getCurrentItem当用户手指离开的屏幕,Page还在动画执行时,就改变了~~难怪~整个滑动过程并不是固定的~~唉,心都碎了~

3、【错误】position在整个滑动的过程中是不变化的,而且ViewPager会保存2个或3个View;那么我考虑,如果是第一页、或者最后一页那么我取getChildAt(0)和getChildAt(1),如果在其他页面则为getChildAt(0),getChildAt(2),然后经过一系列的变化~我想这会总该对了吧,于是我遇到第一问题,第一页的时候,不管左右position都为0,尼玛,这哪个为左View,哪个为右View~~

说了这么多错误,大家可以绕过这些弯路,也能从这些弯路里面看出点什么~

下面说正确的,其实ViewPager在添加一个View或者销毁一个View时,是我们自己的PageAdapter中控制的,于是我们可以在ViewPager里面维系一个HashMap<Position,View>,然后滑动的时候,通过get(position)取出,比如上述效果,始终是右边的View变化,要么从小到大,要么从大到小

 

那么滑倒下一页:左边的View:map.get(position) ,右边的View : map.get(position+1) . 

那么滑倒上一页:左边的View : map.get(position) , 右边的View : map.get(position+1) , 一样的,因为滑到上一页,position为当前页-1

好了,至此,我们分析了且解决了所有步骤。

3、代码

MainActivity

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. package com.example.zhy_jazzyviewpager;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.support.v4.view.PagerAdapter;  
  6. import android.view.Menu;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.ImageView;  
  10. import android.widget.ImageView.ScaleType;  
  11.   
  12. public class MainActivity extends Activity  
  13. {  
  14.     protected static final String TAG = "MainActivity";  
  15.     private int[] mImgIds;  
  16.     private MyJazzyViewPager mViewPager;  
  17.   
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState)  
  20.     {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);  
  23.         mImgIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c,  
  24.                 R.drawable.d };  
  25.         mViewPager = (MyJazzyViewPager) findViewById(R.id.id_viewPager);  
  26.         mViewPager.setAdapter(new PagerAdapter()  
  27.         {  
  28.   
  29.             @Override  
  30.             public boolean isViewFromObject(View arg0, Object arg1)  
  31.             {  
  32.                 return arg0 == arg1;  
  33.             }  
  34.   
  35.             @Override  
  36.             public void destroyItem(ViewGroup container, int position,  
  37.                     Object object)  
  38.             {  
  39.                 container.removeView((View) object);  
  40.             }  
  41.   
  42.             @Override  
  43.             public Object instantiateItem(ViewGroup container, int position)  
  44.             {  
  45.                 ImageView imageView = new ImageView(MainActivity.this);  
  46.                 imageView.setImageResource(mImgIds[position]);  
  47.                 imageView.setScaleType(ScaleType.CENTER_CROP);  
  48.                 container.addView(imageView);  
  49.                 mViewPager.setObjectForPosition(imageView, position);  
  50.                 return imageView;  
  51.             }  
  52.   
  53.             @Override  
  54.             public int getCount()  
  55.             {  
  56.                 return mImgIds.length;  
  57.             }  
  58.         });  
  59.   
  60.     }  
  61.   
  62. }  


这个很常见的代码,就是初始化ViewPager~~就没啥可说的了~~有一点需要注意:在instantiateItem方法,我们多调用了一个mViewPager.setObjectForPosition(imageView, position);其实就是为了给我们的Map存值

 

主要看自定义的ViewPager

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. package com.example.zhy_jazzyviewpager;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.LinkedHashMap;  
  5.   
  6. import android.content.Context;  
  7. import android.support.v4.view.ViewPager;  
  8. import android.util.AttributeSet;  
  9. import android.util.Log;  
  10. import android.view.View;  
  11.   
  12. import com.nineoldandroids.view.ViewHelper;  
  13.   
  14. public class MyJazzyViewPager extends ViewPager  
  15. {  
  16.     private float mTrans;  
  17.     private float mScale;  
  18.     /** 
  19.      * 最大的缩小比例 
  20.      */  
  21.     private static final float SCALE_MAX = 0.5f;  
  22.     private static final String TAG = "MyJazzyViewPager";  
  23.     /** 
  24.      * 保存position与对于的View 
  25.      */  
  26.     private HashMap<Integer, View> mChildrenViews = new LinkedHashMap<Integer, View>();  
  27.     /** 
  28.      * 滑动时左边的元素 
  29.      */  
  30.     private View mLeft;  
  31.     /** 
  32.      * 滑动时右边的元素 
  33.      */  
  34.     private View mRight;  
  35.   
  36.     public MyJazzyViewPager(Context context, AttributeSet attrs)  
  37.     {  
  38.         super(context, attrs);  
  39.     }  
  40.   
  41.     @Override  
  42.     public void onPageScrolled(int position, float positionOffset,  
  43.             int positionOffsetPixels)  
  44.     {  
  45.   
  46. //      Log.e(TAG, "position=" + position+", positionOffset = "+positionOffset+" ,positionOffsetPixels =  " + positionOffsetPixels+" , currentPos = " + getCurrentItem());  
  47.           
  48.         //滑动特别小的距离时,我们认为没有动,可有可无的判断  
  49.         float effectOffset = isSmall(positionOffset) ? 0 : positionOffset;  
  50.           
  51.         //获取左边的View  
  52.         mLeft = findViewFromObject(position);  
  53.         //获取右边的View  
  54.         mRight = findViewFromObject(position + 1);  
  55.           
  56.         // 添加切换动画效果  
  57.         animateStack(mLeft, mRight, effectOffset, positionOffsetPixels);  
  58.         super.onPageScrolled(position, positionOffset, positionOffsetPixels);  
  59.     }  
  60.   
  61.     public void setObjectForPosition(View view, int position)  
  62.     {  
  63.         mChildrenViews.put(position, view);  
  64.     }  
  65.   
  66.     /** 
  67.      * 通过过位置获得对应的View 
  68.      *  
  69.      * @param position 
  70.      * @return 
  71.      */  
  72.     public View findViewFromObject(int position)  
  73.     {  
  74.         return mChildrenViews.get(position);  
  75.     }  
  76.   
  77.     private boolean isSmall(float positionOffset)  
  78.     {  
  79.         return Math.abs(positionOffset) < 0.0001;  
  80.     }  
  81.   
  82.     protected void animateStack(View left, View right, float effectOffset,  
  83.             int positionOffsetPixels)  
  84.     {  
  85.         if (right != null)  
  86.         {  
  87.             /** 
  88.              * 缩小比例 如果手指从右到左的滑动(切换到后一个):0.0~1.0,即从一半到最大 
  89.              * 如果手指从左到右的滑动(切换到前一个):1.0~0,即从最大到一半 
  90.              */  
  91.             mScale = (1 - SCALE_MAX) * effectOffset + SCALE_MAX;  
  92.             /** 
  93.              * x偏移量: 如果手指从右到左的滑动(切换到后一个):0-720 如果手指从左到右的滑动(切换到前一个):720-0 
  94.              */  
  95.             mTrans = -getWidth() - getPageMargin() + positionOffsetPixels;  
  96.             ViewHelper.setScaleX(right, mScale);  
  97.             ViewHelper.setScaleY(right, mScale);  
  98.             ViewHelper.setTranslationX(right, mTrans);  
  99.         }  
  100.         if (left != null)  
  101.         {  
  102.             left.bringToFront();  
  103.         }  
  104.     }  
  105. }  


可以看到,核心代码都是onPageScrolled,我们通过findViewFromObject(position); findViewFromObject(position + 1);分别获取了左右两边的View,然后添加动画效果;当前这个例子添加了两个动画,一个是从0.5放大到1.0或者1.0缩小到0.5,没错由我们的positionOffset提供梯度的变化~~还有个平移的动画:下一页直接移动到当前屏幕(默认是在右边,可以注释这个效果,怎么运行看看),然后不断的通过positionOffsetPixels抵消原来默认移动时的位移,让用户感觉它就在原地放大缩小~~

 

好了,这样就实现了~~你可以随便写自己喜欢的动画效果,比如在默认上面加个淡入淡出或者神马,随便~~是不是很随意~~

我们的布局文件:

 

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.   >  
  6.   
  7.     <com.example.zhy_jazzyviewpager.MyJazzyViewPager  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:id="@+id/id_viewPager" />  
  11.   
  12. </RelativeLayout>  


 

4、JazzyViewPager的使用

其实上面的实现就是github上JazzyViewPager的源码,用法不用说了,就是我们的MainActivity,它内置了大概10来种效果,我们可以通过代码或者布局上面设置动画效果~~我们上面的例子效果,它叫做Stack;

使用JazzViewPager的代码:其实基本一样~~最后也会贴上JazzyViewPager的源码的下载

MainActivity

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. package com.jfeinstein.jazzyviewpager;  
  2.   
  3. import com.jfeinstein.jazzyviewpager.JazzyViewPager.TransitionEffect;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.support.v4.view.PagerAdapter;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.ImageView;  
  11. import android.widget.ImageView.ScaleType;  
  12.   
  13. public class MainActivity extends Activity  
  14. {  
  15.     protected static final String TAG = "MainActivity";  
  16.     private int[] mImgIds;  
  17.     private JazzyViewPager mViewPager;  
  18.   
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState)  
  21.     {  
  22.         super.onCreate(savedInstanceState);  
  23.         setContentView(R.layout.activity_main);  
  24.         mImgIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c,  
  25.                 R.drawable.d };  
  26.         mViewPager = (JazzyViewPager) findViewById(R.id.id_viewPager);  
  27.         //设置切换效果  
  28.         mViewPager.setTransitionEffect(TransitionEffect.Stack);  
  29.           
  30.           
  31.         mViewPager.setAdapter(new PagerAdapter()  
  32.         {  
  33.   
  34.             @Override  
  35.             public boolean isViewFromObject(View arg0, Object arg1)  
  36.             {  
  37.                 return arg0 == arg1;  
  38.             }  
  39.   
  40.             @Override  
  41.             public void destroyItem(ViewGroup container, int position,  
  42.                     Object object)  
  43.             {  
  44.                 container.removeView((View) object);  
  45.             }  
  46.   
  47.             @Override  
  48.             public Object instantiateItem(ViewGroup container, int position)  
  49.             {  
  50.                 ImageView imageView = new ImageView(MainActivity.this);  
  51.                 imageView.setImageResource(mImgIds[position]);  
  52.                 imageView.setScaleType(ScaleType.CENTER_CROP);  
  53.                 container.addView(imageView);  
  54.                 mViewPager.setObjectForPosition(imageView, position);  
  55.                 return imageView;  
  56.             }  
  57.   
  58.             @Override  
  59.             public int getCount()  
  60.             {  
  61.                 return mImgIds.length;  
  62.             }  
  63.         });  
  64.   
  65.     }  
  66.   
  67. }  


与我们的代码唯一区别就是:

 

//设置切换效果
mViewPager.setTransitionEffect(TransitionEffect.Stack);

它有12中可选的切换效果,其实就是写了12个切换的动画~~~

好了,最后附上一个我比较喜欢的效果:Tablet

最后,喜欢借此博客抛砖引玉~~大家对感兴趣的github上的代码,可以进行分析与自己尝试去实现,有时候会发现不是很难~你也可以做到~!

更多详情:近乎sns开发分享社区

来源csdn源码下载http://download.csdn.net/download/lmj623565791/7663605

加载中
0
TiHou
TiHou
很好,值得借鉴和学习
0
思落羽
思落羽
请问如果希望前后两张图片都留下一部分在界面上,应该如何实现?
0
亓斌哥哥
亓斌哥哥
csdn上的博客吧。。
0
h
hellozhiwei
学习了,真不错啊
返回顶部
顶部