自定义ViewPager实现3D画廊效果


本文源码请看github地址:https://github.com/AweiLoveAndroid/My3DViewPager


经常在群里看到有些开发者在提问:怎么实现3D画廊效果,没思路。

有人出谋划策,你重写onTouch,在里面去判断;或者你去重写滑动监听事件,滑动的时候去动态设置左右两边的图片的大小和缩放效果。可能你们去那样写可以实现,不过时间太长,项目时间紧急的时候,根本来不及写。怎么办呢?没关系,跟着我的思路走一下,你就知道了。

首先, 试着猜想一下,既然是与页面滑动有关,那么ViewPager肯定有setPageXXX之类的方法,那么我们试着找一下:

果然我们看到了有一个很关键的方法:(其他几个一看就很清楚把:设置间距,背景,显示页数的,监听)

setPageTransformer(XXX);
这是一个关键方法

我们接着看看源码,看看到底是什么鬼:

Paste_Image.png

有人会说我的英语不好,看不懂这段话,,没关系,万能的谷歌帮了我们(现在谷歌翻译是不需要*的),我们看看重要的翻译:

# 设置pagetransformer,这允许应用程序将自定义属性转换应用到每个页面,重写默认的滑动行为。

参数2是:Pagetransformer对象,它是ViewPager里面的一个接口。

Paste_Image.png
翻译一下Pagetransformer接口的注释的意思:
每当可见/附加页面滚动时,就会调用一个PageTransformer。这为应用程序提供了使用动画属性将自定义转换应用于页面视图的机会。

transformPage方法的说明:将属性转换应用于给定页面。
    参数1 page   将转换应用于此页面
    参数2 position    页面相对于pager的当前中心位置的位置。 0是正面和中心。 1是右侧的一个整页位置,-1是左侧的一页位置。

ok,既然找到了关键所在,那么好久开始撸码!开干!

1.写一个类实现ViewPager.PageTransformer,重写transformPage方法

/**
 * 注意:ViewPager要用V4包里面的,别导错包了
 * 用ViewPager实现3D画廊效果
 */
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);
        } else if (position < 0){//position从0变化到-1,page逐渐向左滑动
            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);
            page.setRotationY(rotate);
        } else if (position >=0 && position < 1){//position从0变化到1,page逐渐向右滑动
            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);
            page.setRotationY(-rotate);
        } else if (position >= 1){//position大于等于1的时候,代表page已经位于中心item的最右边
            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);
            page.setRotationY(-rotate);
        }
    }
}

2.自定义ViewPager的适配器,这个很简单了,就不多讲了,直接上代码:

public class MyPagerAdapter extends PagerAdapter {
private int[] mBitmapIds;
private Context mContext;
private LruCache<Integer,Bitmap> mCache;

public MyPagerAdapter(int[] data,Context context){
    this.mBitmapIds = data;
    this.mContext = context;

    //以下两个变量是做缓存处理
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    int cacheSize = maxMemory * 3 / 8;  //缓存区的大小
    mCache = new LruCache<Integer, Bitmap>(cacheSize){
        @Override
        protected int sizeOf(Integer key, Bitmap value) {
            return value.getRowBytes() * value.getHeight();  //返回Bitmap的大小
        }
    };
}

@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]);
    //new LoadBitmapTask(imageView).execute(mBitmapIds[position]);
    loadBitmapIntoTarget(mBitmapIds[position],imageView);
    container.addView(view);
    return view;
}


@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    container.removeView((View) object);
}


/**
 * 看有没有缓存,有就开启异步加载,没有就直接加载图片
 * @param id
 * @param imageView
 */
public void loadBitmapIntoTarget(Integer id, ImageView imageView){
    //真正开发中是要做三级缓存处理的,这里都是用的本地图片,就没有做处理。
    //如果你想试试,可以在tomcat里面放几个图片,试试从服务器获取图片,然后去做三级缓存处理
    //我这里简化操作,只简洁的说一下基本的思路
    //首先尝试从内存缓存中获取是否有对应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;
}


//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<Integer,Void,Bitmap> {
    private ImageView imageView;

    public LoadBitmapTask(ImageView imageView){
        this.imageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(Integer... params) {
        //这样做没有做缓存处理,加载大量大图容易出现OOM
        //Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),params[0]);
        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);
    }


}

}

3.在MainAvtivity里面测试一下。

/**
 * 在这里测试一下,看看效果
 */
public class MainActivity extends AppCompatActivity {
    //这里的图片自己去随便找几张吧
    private static final int[] drawableIds = new int[]{R.mipmap.ic1,R.mipmap.ic2, R.mipmap.ic3,
            R.mipmap.ic4, R.mipmap.ic5, R.mipmap.ic6, R.mipmap.ic7, R.mipmap.ic8,
            R.mipmap.ic9, R.mipmap.ic10, R.mipmap.ic11, R.mipmap.ic12, R.mipmap.ic13};
    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);
        mViewPager.setPageTransformer(true,new RotationPageTransformer());
        mViewPager.setOffscreenPageLimit(2);//设置预加载的数量,这里设置了2,会预加载中心item左边两个Item和右边两个Item
        mViewPager.setPageMargin(10);//设置两个Page之间的距离
    }
}

4.布局里面就是一个ViewPager,id为viewpager,这里就不写了。

OK,下面来看一下效果图吧,是不是很酷呢?

3DViewPager.gif
上一篇:深入理解什么是RESTful API ?


下一篇:windows平台vhd磁盘文件挂载