前言

对于ViewPager,相信大家都已经很熟悉了,在各种切换场景比如Fragment切换、选项卡的切换或者顶部轮播图片等都可以用ViewPager去实现。那么本篇文章带来ViewPager的一种实现效果:3D画廊。直接上图来看:

ic.gif

从上面的图我们可以看出,整个页面分成三个部分,中间的是大图,正中地显示给用户;而两边的是侧图,而这两幅图片又有着角度的旋转,与大图看起来不在同一平面上,这就形成了3D效果。接着拖动页面,侧面的图慢慢移到中间,这个过程也是有着动画的,包括了图片的旋转、缩放和平移。在欣赏了上面的效果后,话不多说,我们来看看是怎样实现的。

实现原理

1、利用ViewGroup的clipChildren属性。大家可能对ClipChildren属性比较陌生,我们先来看看官方文档对该属性的描述:

Defines whether a child is limited to draw inside of its bounds or not. This is useful with animations that scale the size of the children to more than 100% for instance. In such a case, this property should be set to false to allow the children to draw outside of their bounds. The default value of this property is true.

上面的大意是说,ViewGroup的子View默认是不会绘制边界意外的部分的,倘若将clipChildren属性设置为false,那么子View会把自身边界之外的部分绘制出来。

那么这个属性跟我们的ViewPager又有什么关联呢?我们可以这样想,ViewPager自身是一个ViewGroup,如果将它的宽度限制为某一个大小比如200dp(我们通常是match_parent),这样ViewPager的绘制区域就被限制在了240dp内(此时绘制的是ViewA),此时我们将它的父容器的clipChildren属性设置为false,那么ViewPager未绘制的部分就会在两旁得到绘制(此时绘制的是ViewA左右两边的Item View)。

那么我们的布局文件可以这样写,activity_main.xml:

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_main"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:clipChildren="false">

android:id="@+id/viewpager"

android:layout_width="240dp"

android:layout_height="match_parent"

android:clipChildren="false"

android:layout_centerInParent="true">

接着,我们需要为每个Item创建一个布局,这个很简单,就是一个ImageView,新建item_main.xml文件:

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/iv"

android:layout_width="240dp"

android:layout_height="360dp"

android:layout_centerInParent="true"/>

布局文件写好后,我们接着完成MainActivity.java和MyPagerAdapter.java的内容:

MainActivity.java:

public class MainActivity extends AppCompatActivity {

//这里的图片从百度图片中下载,图片规格是960*640

private static final int[] drawableIds = new int[]{R.mipmap.ic_01,R.mipmap.ic_02,R.mipmap.ic_03,

R.mipmap.ic_04,R.mipmap.ic_05,R.mipmap.ic_06,R.mipmap.ic_07,R.mipmap.ic_08,R.mipmap.ic_09,

R.mipmap.ic_10,R.mipmap.ic_11,R.mipmap.ic_12};

private ViewPager mViewPager;

private RelativeLayout mRelativeLayout;

private MyPagerAdapter mPagerAdapter;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initViews();

}

private void initViews() {

mViewPager = (ViewPager) findViewById(R.id.viewpager);

mPagerAdapter = new MyPagerAdapter(drawableIds,this);

mViewPager.setAdapter(mPagerAdapter);

}

}

MyPagerAdapter.java:

public class MyPagerAdapter extends PagerAdapter {

private int[] mBitmapIds;

private Context mContext;

public MyPagerAdapter(int[] data,Context context){

mBitmapIds = data;

mContext = context;

}

@Override

public int getCount() {

return mBitmapIds.length;

}

@Override

public boolean isViewFromObject(View view, Object object) {

return view == object;

}

@Override

public Object instantiateItem(ViewGroup container, int position) {

View view = LayoutInflater.from(mContext).inflate(R.layout.item_main,container,false);

ImageView imageView = (ImageView) view.findViewById(R.id.iv);

imageView.setImageResource(mBitmapIds[position]);

container.addView(view);

return view;

}

@Override

public void destroyItem(ViewGroup container, int position, Object object) {

container.removeView((View) object);

}

}

ok,到现在为止,我们先运行一下看看结果如何:

ic_01.png

从上图可以看出,本来ViewPager设置的宽度是240dp,那么原来应该只会显示一个Page的内容,但是由于clipChildren=false属性的生效,使得ViewPager早240dp之外的部分也被绘制了出来。那么到目前为止,就实现了在一屏显示多个Page的效果了,那么接下来的3D效果怎样实现呢?

2、利用ViewPager.PageTransformer实现滑动动画效果

PageTransformer是Android3.0之后加入的一个接口,通过该接口我们可以方便地为ViewPager添加滑动动画,但是该接口只能用于Android3.0之后的版本,3.0之前的版本会被忽略。我们看看这个接口需要重写的唯一一个方法:

/**

* A PageTransformer is invoked whenever a visible/attached page is scrolled.

* This offers an opportunity for the application to apply a custom transformation

* to the page views using animation properties.

*

*

As property animation is only supported as of Android 3.0 and forward,

* setting a PageTransformer on a ViewPager on earlier platform versions will

* be ignored.

*/

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);

}

通过官方的注释,我们可以获得如下信息:①PageTransformer在可见Item或者被添加到ViewPager的Item的位置发生改变的时候,就会回调该方法。可见Item很容易理解,就是当前被选中的Page,那么attached page怎样理解呢?我们知道,ViewPager有着预加载机制,默认的预加载数量是1,即中心Item向左的一个Item以及向右的一个Item,由于预加载机制的存在使得ViewPager在滑动的过程中不会感到卡顿,因为需要展示的页面已经提前准备好了。

②关注transformPage(page,position)的方法参数,这里的position是存在一个范围的,0代表当前被选中的Page的位置,位于中心,如果当前Page向左滑动,那么position会从0减到-1,当Page向右滑动,position会从0增加到1。当一个page的position变为-1的时候,这个page便位于中心Item的左边了,相对的,position变成1的时候,这个page便位于中心Item的右边。利用这个position变化的性质,我们可以很轻松地对View的某些属性进行改变了。

接下来,新建RotationPageTransformer.java文件:

public class RotationPageTransformer implements ViewPager.PageTransformer {

private static final float MIN_SCALE=0.85f;

@Override

public void transformPage(View page, float position) {

float scaleFactor = Math.max(MIN_SCALE,1 - Math.abs(position));

float rotate = 10 * Math.abs(position);

//position小于等于1的时候,代表page已经位于中心item的最左边,

//此时设置为最小的缩放率以及最大的旋转度数

if (position <= -1){

page.setScaleX(MIN_SCALE);

page.setScaleY(MIN_SCALE);

page.setRotationY(rotate);

}//position从0变化到-1,page逐渐向左滑动

else if (position < 0){

page.setScaleX(scaleFactor);

page.setScaleY(scaleFactor);

page.setRotationY(rotate);

}//position从0变化到1,page逐渐向右滑动

else if (position >=0 && position < 1){

page.setScaleX(scaleFactor);

page.setScaleY(scaleFactor);

page.setRotationY(-rotate);

}//position大于等于1的时候,代表page已经位于中心item的最右边

else if (position >= 1){

page.setScaleX(scaleFactor);

page.setScaleY(scaleFactor);

page.setRotationY(-rotate);

}

}

}

接着,我们为ViewPager设置这样一个属性即可:

mViewPager.setPageTransformer(true,new RotationPageTransformer());

mViewPager.setOffscreenPageLimit(2); //下面会说到

我们运行一下代码,会发现结果跟最上面展示的效果图是一样的,此时滑动ViewPager,各个Item之间的切换也会有动画的出现,呈现出了3D效果。

3、setPageMargin(int)方法,PageMargin属性用于设置两个Page之间的距离,有需要的可以加上该属性,使得两个Page的区分更加明显。

4、setOffscreenPageLimit(int)方法,OffscreenPageLimit属性用于设置预加载的数量,比如说这里设置了2,那么就会预加载中心item左边两个Item和右边两个Item。那么这里这个属性对于我们的3D效果有什么影响呢?我们来试验一下,首先调用mViewPager.setOffscreenPageLimit(1),把预加载数量设置为1,然后运行程序,向左右滑动几次,会发现出现了下面的问题:

ic_02.png

即左边或者右边的Item在滑动的过程中有可能出现不正确的显示,这是为什么呢?其实这是预加载的数量的问题,当前如果处于position为0的情况下,此时已经预加载了position为1的Item,那么该Item能正常显示,然而当滑动的时候,由于ViewPager是停止滑动的时候才会加载需要的Item,导致滑动到item1的时候,已经没有需要显示的Item2了(因此此时尚未加载),但是当手指松开的时候,Item2得到加载,但是此时不再调用transformPage()方法来调整自身的显示,所以造成了上面的错误显示。解决的办法是可以把预加载的数量设置为2或者3,这样得到的效果更好。

优化

在实现以上效果后,我们需要重新审视一遍我们的代码,看看是否还有优化的空间。

1、我们在Adapter中的instantiateItem()方法内加载一个View,并用了ImageView的setImageResource()方法来加载图片,其实查看该方法的源码可知,这个方法是在UI线程内加载图片的,如果加载的是很大的一张图片,那么就造成了UI线程的拥堵。

2、对于已经加载的图片,没有得到充分的利用,而是每次都加载一次,而旧的图片由于失去了引用又处于待回收的状态,这样不断的加载和回收无疑是加重了系统的负担。

3、如果ImageView的宽高小于图片的规格,那么把完整的一个大图加载到ImageView内,显然也是不合适的。因为图片越大的话,其占用的内存也越大。

针对上述所说的情况,我们可以一一找到对应的解决办法:

1、对于在UI线程加载图片的情况,我们可以考虑在子线程加载图片,等图片加载完毕后在通知主线程把图片设置进ImageView内即可。自然我们会想到使用Handler来进行线程之间的通信。但是这又引发一个问题,如果每一次的instantiateItem()方法内我们都新开一条线程去加载图片,那么最终的结果是创建了很多只用了一次的线程,这样的开销更大了。那有没有可以控制子线程的方法呢?答案是线程池。线程池通过合理调度线程的使用,使得线程达到最大的使用效率。那么我们可以直接使用AsyncTask来实现以上功能,因为AsyncTask内部也用到了线程池。

我们在MyPagerAdapter.java内新建一个内部类:

private class LoadBitmapTask extends AsyncTask{

private ImageView imageView;

public LoadBitmapTask(ImageView imageView){

this.imageView = imageView;

}

@Override

protected Bitmap doInBackground(Integer... params) {

Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),params[0]);

return bitmap;

}

@Override

protected void onPostExecute(Bitmap bitmap) {

imageView.setImageBitmap(bitmap);

}

}

然后在instantiateItem()方法内添加如下代码:new LoadBitmapTask(imageView).execute(mBitmapIds[position]);这样便开启了异步任务,在后台线程内加载我们的图片。

2、对于高效利用已经加载好的图片,我们可以这样理解:因为如果一个Item被destroy后,它就会从它的父容器中移除,然后它的drawable(已经设置好的Bitmap)接着会在某个时刻被gc回收。但是,用户可能会来回滑动页面,那么之前的无用Bitmap其实可以再度利用,而不是重新加载一遍。自然,我们可以想到的是利用LruCache来进行内存缓存,对Bitmap保存一个强引用,这样就不会被gc回收,等到需要用的时候再返回这个Bitmap,对不常用的bitmap进行回收即可。这样便提高了Bitmap的利用效率,不会重复加载Bitmap,也能使内存的消耗保存在一个合理的范围之内。使用LruCache也很简单:

①首先我们在MyPagerAdapyer的构造方法内初始化LruCache:

public MyPagerAdapter(int[] data,Context context){

mBitmapIds = data;

mContext = context;

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

int cacheSize = maxMemory * 3 / 8; //缓存区的大小

mCache = new LruCache(cacheSize){

@Override

protected int sizeOf(Integer key, Bitmap value) {

return value.getRowBytes() * value.getHeight(); //返回Bitmap的大小

}

};

}

②新建一个方法:

public void loadBitmapIntoTarget(Integer id,ImageView imageView){

//首先尝试从内存缓存中获取是否有对应id的Bitmap

Bitmap bitmap = mCache.get(id);

if (bitmap != null){

imageView.setImageBitmap(bitmap);

}else {

//如果没有则开启异步任务去加载

new LoadBitmapTask(imageView).execute(id);

}

}

③对LoadBitmapTask作微小的修改,主要是在异步加载任务之后,向内存缓存中添加bitmap:

private class LoadBitmapTask extends AsyncTask{

@Override

protected Bitmap doInBackground(Integer... params) {

Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),params[0]);

//把加载好的Bitmap放进LruCache内

mCache.put(params[0],bitmap);

return bitmap;

}

}

④最后,在我们的instantiate()方法内调用我们的loadBitmapIntoTarget方法即可:

loadBitmapIntoTarget(mBitmapIds[position],imageView);

3、对于最后一种情况,我们可以考虑在加载图片之前,对图片进行缩放,使得图片的规格符合ImageView,那么就不会造成内存的浪费了,那么怎样对一个Bitmap进行缩放呢?

我们知道,一般加载图片都是利用BitmapFactory的几个decode方法来加载,但我们观察这几个方法,会发现它们各自还有一个带options参数的重载方法,即BitmapFactory.Options,那么Bitmap的缩放玄机就在这个Options内。Options有一个成员变量:inSampleSize,采样率,即设置对Bitmap的采样率,比如说inSampleSize默认为1,此时Bitmap的采样宽高等于原始宽高,不做任何改变。如果inSampleSize等于2,那么采样宽高都为原始宽高的1/2,那么大小就变成了原始大小的1/4,因此利用好这个inSampleSize能很好地控制一个Bitmap的大小。具体的使用方法可参考如下:

private int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){

int height = options.outHeight;

int width = options.outWidth;

int inSampleSize = 1;

if (height >= reqHeight || width > reqWidth){

while ((height / (2 * inSampleSize)) >= reqHeight

&& (width / (2 * inSampleSize)) >= reqWidth){

inSampleSize *= 2;

}

}

return inSampleSize;

}

//dp转换成px

public static int dp2px(Context context, float dpValue) {

final float scale = context.getResources().getDisplayMetrics().density;

return (int) (dpValue * scale + 0.5f);

}

private class LoadBitmapTask extends AsyncTask{

@Override

protected Bitmap doInBackground(Integer... params) {

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true; //1、inJustDecodeBounds置为true,此时只加载图片的宽高信息

BitmapFactory.decodeResource(mContext.getResources(),params[0],options);

options.inSampleSize = calculateInSampleSize(options,

dp2px(mContext,240),

dp2px(mContext,360)); //2、根据ImageView的宽高计算所需要的采样率

options.inJustDecodeBounds = false; //3、inJustDecodeBounds置为false,正常加载图片

Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),params[0],options);

//把加载好的Bitmap放进LruCache内

mCache.put(params[0],bitmap);

return bitmap;

}

}

有一点要说明的是,笔者这里使用的图片是960 * 640的,比ImageView的宽高要小,所以体现不出图片的缩放,读者可以自行改变ImageView的大小,或者加载一张更大规格的图片。

最后,放上修改后MyPagerAdapter.java的完整代码,以供读者参考:

public class MyPagerAdapter extends PagerAdapter {

private int[] mBitmapIds;

private Context mContext;

private LruCache mCache;

public MyPagerAdapter(int[] data,Context context){

mBitmapIds = data;

mContext = context;

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

int cacheSize = maxMemory * 3 / 8; //缓存区的大小

mCache = new LruCache(cacheSize){

@Override

protected int sizeOf(Integer key, Bitmap value) {

return value.getRowBytes() * value.getHeight();

}

};

}

@Override

public int getCount() {

return mBitmapIds.length;

}

@Override

public boolean isViewFromObject(View view, Object object) {

return view == object;

}

@Override

public Object instantiateItem(ViewGroup container, int position) {

View view = LayoutInflater.from(mContext).inflate(R.layout.item_main,container,false);

ImageView imageView = (ImageView) view.findViewById(R.id.iv);

loadBitmapIntoTarget(mBitmapIds[position],imageView);

container.addView(view);

return view;

}

@Override

public void destroyItem(ViewGroup container, int position, Object object) {

container.removeView((View) object);

}

public void loadBitmapIntoTarget(Integer id,ImageView imageView){

//首先尝试从内存缓存中获取是否有对应id的Bitmap

Bitmap bitmap = mCache.get(id);

if (bitmap != null){

imageView.setImageBitmap(bitmap);

}else {

//如果没有则开启异步任务去加载

new LoadBitmapTask(imageView).execute(id);

}

}

private int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){

int height = options.outHeight;

int width = options.outWidth;

int inSampleSize = 1;

if (height >= reqHeight || width > reqWidth){

while ((height / (2 * inSampleSize)) >= reqHeight

&& (width / (2 * inSampleSize)) >= reqWidth){

inSampleSize *= 2;

}

}

return inSampleSize;

}

public static int dp2px(Context context, float dpValue) {

final float scale = context.getResources().getDisplayMetrics().density;

return (int) (dpValue * scale + 0.5f);

}

private class LoadBitmapTask extends AsyncTask{

private ImageView imageView;

public LoadBitmapTask(ImageView imageView){

this.imageView = imageView;

}

@Override

protected Bitmap doInBackground(Integer... params) {

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true; //1、inJustDecodeBounds置为true,此时只加载图片的宽高信息

BitmapFactory.decodeResource(mContext.getResources(),params[0],options);

options.inSampleSize = calculateInSampleSize(options,

dp2px(mContext,240),

dp2px(mContext,360)); //2、根据ImageView的宽高计算所需要的采样率

options.inJustDecodeBounds = false; //3、inJustDecodeBounds置为false,正常加载图片

Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),params[0],options);

//把加载好的Bitmap放进LruCache内

mCache.put(params[0],bitmap);

return bitmap;

}

@Override

protected void onPostExecute(Bitmap bitmap) {

imageView.setImageBitmap(bitmap);

}

}

}

最后,感谢你的阅读,希望这篇文章对你有所帮助~

android 3d翻转动画 viewpage,利用ViewPager实现3D画廊效果及其图片加载优化相关推荐

  1. Android图片加载优化方案

    1. 前言 在电商APP中,图片在整个页面中占比最大,清晰高质量的图片能够明显提升转化率.但是APP运行环境错综复杂,往往我们会遇到 图片压缩导致模糊.列表加载长时间显示空白图.查看大图黑屏过久.甚至 ...

  2. Android图片加载优化

    一.高效加载大图片 我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状.不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小.比如说系统图片库里展示的图片大 ...

  3. Android开发教程 - 使用Data Binding(七)使用BindingAdapter简化图片加载

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  4. android 图标弹跳动画,动效教程 | 5 分钟快速做个弹跳加载小动画

    小编:APP的动效现今是越来越重要了,以前是加分项,现在是必会技能-今天给大家带来的是AE动效教程 -- 5 分钟快速做个弹跳加载小动画,一起来学习学习吧-- 前言 哈喽,今天给大家解禁一个墨染动效特 ...

  5. Android图片加载库—Picasso一个强大的图像下载和缓存库

    介绍 GitHub源码 点击查看 Picasso是一款强大的图片下载和缓存开源软件,只能在Android平台上使用,由Square开发.使用Picasso可以添加一些必须的特性和视觉效果到Androi ...

  6. Android 登录3D翻转动画效果

    Android 登录3D翻转动画效果 描述:这是一个 登录3D翻转效果的Demo. 项目代码在最后面!!!!跳转到最后 控件效果如下: 实现功能: 使页面进行3D翻转(3D翻转效果) 可通过回调监听两 ...

  7. php 3d animation,CSS_纯CSS实现菜单、导航栏的3D翻转动画效果,我曾经向大家展示过闪光的logo - phpStudy...

    纯CSS实现菜单.导航栏的3D翻转动画效果 我曾经向大家展示过闪光的logo,燃烧的火狐狸,多重嵌套动画等例子,今天,我们将要制作一个简单但非常酷的3D翻转菜单.大家可以先看看实际效果,下面有效果截图 ...

  8. Android 卡片翻转动画效果

    转载请标明出处:http://blog.csdn.net/android_mnbvcxz/article/details/78570594 Android 卡片翻转动画效果 前言 前端时间开发一款应用 ...

  9. html5 鼠标旋转动画效果,CSS3鼠标滑过图片3D翻转动画特效

    这是一款效果非常炫酷的CSS3鼠标滑过图片3D翻转动画特效.该特效基于Bootstrap网格系统来布局,通过简单的CSS3代码,在鼠标滑过图片时对图片进行3D翻转,效果非常的酷. 使用方法 HTML结 ...

  10. android圆球触摸怎么实现,Android利用ViewPager实现带小圆球的图片滑动

    Android利用ViewPager实现带小圆球的图片滑动 发布时间:2020-10-02 05:55:29 来源:脚本之家 阅读:69 作者:antimage08 在上文实现的带小圆球的图片滑动的通 ...

最新文章

  1. 设置select下拉框不可修改的→“四”←种方法
  2. NPOI读写Excel
  3. 图片增强深度学习deep learning keras
  4. oracle时间运算
  5. 30分钟内使用MongoDB
  6. 《机器学习基石》---感知机算法
  7. 【Python】SyntaxError: Non-UTF-8 code starting with ‘\xe5‘ in file XXX.py on line XX 的解决方法
  8. Solaris 中的环境变量
  9. SysTick系统定时器(功能框图和优先级配置)
  10. Java Web(1)高并发业务
  11. 电信笔试C语言,2021中国电信考试试题附答案(12)
  12. 杨辉三角python小代码
  13. RxJava结合Retrofit使用 自定义封装类结合泛型 请求网络数据+Fresco+RecyclerView+MVP分层
  14. hive中reduce类函数说明
  15. 友盟分享,极光推送Demo
  16. cad画直角命令_cad中怎么把直角倒角
  17. 使用TensorFlow搭建智能开发系统,自动生成App UI代码
  18. Keil软件的方波仿真
  19. LaTex单词的间隔
  20. 数据库技术:关系型数据库设计总结

热门文章

  1. VS Code Css格式化插件
  2. windows操作系统下新建txt文件快捷键
  3. Java 正则表达式 匹配英文字母
  4. 软件用户手册编写指南(GB8567一88/GB8567一2006)
  5. TFS2010安装全过程
  6. 七上八下猜数字_猜数字游戏,猜别人心里想的数字
  7. 数字图像处理技术在智能交通中的应用
  8. php机器人聊天对话框,PHP实现QQ聊天机器人
  9. 服务器不能用pe安装win7系统安装,WinPE无法安装win7系统的完美解决方案
  10. java 调用 swf 文件上传,swfupload 文件 上传