android 自定义控件实现3D画廊效果

今天来实现一个3D画廊的效果,这个效果的难点在于计算旋转角度,当然里面会有好几个知识点要讲,针对Paint的2个方法,一个是setShader(),一个是setXfermode(),首先看下大概的效果,

大概是这种,这是我在网上随便找了一个类似的图片,因为我的效果还没写,没关系,这没啥影响,这个效果我准备分开写,然后后面合成起来,上面的效果可以分为如下几步

1:首先是怎么截取一张图片中的一部分

2:怎么把多张图片合成一张图片

3:怎么生成倒影效果,

4:怎么改变倒影中的图片透明度

5:最后一步是怎么计算它滑动时候的旋转角度问题

首先把第一步的效果实现出来,就是怎么截图一张图片中的一部分,如图:

现在开始写代码

public class MainActivity extends Activity {
    private ImageView iv,originalIv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
        originalIv = (ImageView) findViewById(R.id.originalIv);
        originalIv.setImageResource(R.mipmap.gird);
        Bitmap bitmap = compoundBitmap(R.mipmap.gird);
        iv.setImageBitmap(bitmap);
    }
    /**
     * 截图图片
     * @param resId 图片 id
     */
    public Bitmap compoundBitmap(int resId){
        Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(),resId);//把资源图片变成一个Bitmap对象
        //生成下面的一半图片
        Matrix matrix = new Matrix();
        Bitmap invertBitmap = Bitmap.createBitmap(originalBitmap,0,originalBitmap.getHeight()/2,originalBitmap.getWidth(),originalBitmap.getHeight()/2,matrix,false);
        return  invertBitmap;
    }
}

效果:第一个而是原图 第二张是截图一半的图,为了进行更好的对比,

上面就使用了一个Bitmap的createBitmap方法就可以实现截图了,现在对createBitmap的方法参数进行说明

public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

参数说明:

source:原位图

x:位图的x轴坐标

y:位图的y轴坐标

width:新位图的宽度

height:新位图的高度

m:矩阵

filter:这个参数比较难懂 先看下google官网文档介绍

 true if the source should be filtered.
*                   Only applies if the matrix contains more than just
*                   translation

当进行的不只是平移变换时,filter参数为true可以进行滤波处理,有助于改善新图像质量;flase时,计算机不做过滤处理

画图解释下:

现在怎么考虑把这二张图片合成一张图片了,这个要用到Canvas了,之前我们canvas是从自定义view中的onDraw()方法给我们提供的,现在我们要创建一个画布,然后在这画布上把这二张图片画上去,

代码如下:

/**
 * 合成图片
 * @param resId 图片 id
 */
public Bitmap compoundBitmap(int resId){
    Paint paint = new Paint();
    Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(),resId);//把资源图片变成一个Bitmap对象
    //生成下面的一半图片
    Matrix matrix = new Matrix();
    Bitmap invertBitmap = Bitmap.createBitmap(originalBitmap,0,originalBitmap.getHeight()/2,originalBitmap.getWidth(),originalBitmap.getHeight()/2,matrix,false);
    //创建一个空的位图
    Bitmap compoundBitmap =  Bitmap.createBitmap(originalBitmap.getWidth(),originalBitmap.getHeight()+invertBitmap.getHeight(), Bitmap.Config.ARGB_8888);

    Canvas canvas = new Canvas(compoundBitmap);
    canvas.drawBitmap(originalBitmap,0,0,paint);
    canvas.drawBitmap(invertBitmap,0,originalBitmap.getHeight(),paint);

    return  compoundBitmap;
}

效果:

ok,现在我们实现了2张图片怎么合成一张图片,首先是先绘制一个空的位图,这个位图的宽和高要设置好,然后通过canvas的drawBitmap()把这二张图片绘制到画布上去,绘制上要注意坐标点就行,还有个问题就是截图的一半图片不是垂直的拼接在下面,上面我们讲截图图片一部分的方法createBitmap()方法进行参数说明的时候,其中有一个Matirs矩阵,当时为什么要使用这个方法呢?Bitmap的createBitmap有很多重载的方法,就是因为提供了Matris类,让我们可以对位图进行像动画那样操作,现在看下Matirs一些常用的方法:

我们发现Matris提供了平移,缩放,旋转,透明等操作,我们只要加入一行代码就可以让下面的一半图片改成在原来的图片上进行垂直后显示,

matrix.setScale(1,-1);

public void setScale(float sx, float sy)

参数说明:

sx:x轴水平翻转 -1表示翻转 1表示不翻转

sy:y轴方向翻转  -1表示翻转
1表示不翻转

/**
 * 合成图片
 * @param resId 图片 id
 */
public Bitmap compoundBitmap(int resId){
    Paint paint = new Paint();
    Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(),resId);//把资源图片变成一个Bitmap对象
    //生成下面的一半图片
    Matrix matrix = new Matrix();
    matrix.setScale(1,-1);
    Bitmap invertBitmap = Bitmap.createBitmap(originalBitmap,0,originalBitmap.getHeight()/2,originalBitmap.getWidth(),originalBitmap.getHeight()/2,matrix,false);
    //创建一个空的位图
    Bitmap compoundBitmap =  Bitmap.createBitmap(originalBitmap.getWidth(),originalBitmap.getHeight()+invertBitmap.getHeight()+10, Bitmap.Config.ARGB_8888);//+10是为了2张图片之间有空隙

    Canvas canvas = new Canvas(compoundBitmap);
    canvas.drawBitmap(originalBitmap,0,0,paint);
    canvas.drawBitmap(invertBitmap,0,originalBitmap.getHeight()+10,paint);
    return  compoundBitmap;
}

效果:

现在我们把前三步实现了,还有个倒影图片的透明度问题,这就使用到Paint中的setShader()方法了,这是设置画笔的颜色渲染方法,发现Shader有5个子类,每一个子类实现的功能效果不一样,

Shader的直接子类以及作用:

BitmapShader    : 位图图像渲染

LinearGradient  : 线性渲染

RadialGradient  : 环形渲染

SweepGradient   : 扫描渐变渲染/梯度渲染

ComposeShader   : 组合渲染,可以和其他几个子类组合起来使用

现在举例讲每个大概的功能,见识下平时效果使我们想不到是通过这个api实现的,

BitmapShader 

先看下它的构造函数:

public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY)

参数说明:

bitmap:渲染器使用的位图

tileX:在位图x轴方向渲染器的平铺模式

tileY:在位图y轴方向渲染器的平铺模式

渲染器的模式有如下三种:

public enum TileMode {
    /**
     * replicate the edge color if the shader draws outside of its
     * original bounds
     */
    CLAMP   (0),
    /**
     * repeat the shader‘s image horizontally and vertically
     */
    REPEAT  (1),
    /**
     * repeat the shader‘s image horizontally and vertically, alternating
     * mirror images so that adjacent images always seam
     */
    MIRROR  (2);

    TileMode(int nativeInt) {
        this.nativeInt = nativeInt;
    }
    final int nativeInt;
}

CLAMP:超过边缘部分会在超过边缘范围给染色  也就是拉伸  比如imageview大小为100,100,但是bitmap大小为80,80如果使用了这个模式,会把其余的20都给染色

REPEAT:横向和纵向的重复渲染器图片,平铺

MIRROR:横向不断翻转重复,纵向不断翻转重复

我们平时使用比较多的圆角图片也可以使用这个实现

public class CustomImageView extends View {
    private Paint mPaint;
    private ShapeDrawable mShapeDrawable;
    private Bitmap shaderBitmap;
    private int bitmapWidth;
    private int bitmapHeight;
    private BitmapShader mBitmapShader;
    public CustomImageView(Context context) {
        this(context, null);
    }

    public CustomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mShapeDrawable = new ShapeDrawable(new OvalShape());
        mShapeDrawable.getPaint().setShader(mBitmapShader);
        //这是设置一个矩形区域  表示这个drawable所绘制的区域
        mShapeDrawable.setBounds(20, 20, bitmapWidth, bitmapHeight);
        mShapeDrawable.draw(canvas);

    }
    public void setImageRes(int resId){
        shaderBitmap = BitmapFactory.decodeResource(getContext().getResources(),resId);
        bitmapWidth = shaderBitmap.getWidth();
        bitmapHeight = shaderBitmap.getHeight();
        mBitmapShader = new BitmapShader(shaderBitmap, Shader.TileMode.MIRROR,Shader.TileMode.REPEAT);
    }
}

效果:

其实还有别的图形模式,比如圆角矩形

public ShapeDrawable(Shape s) {
    this(new ShapeState(null), null);

    mShapeState.mShape = s;
}

多个Shape子类

这样好像看起来不容易理解,现在再举个例子,我先准备一张图片,

public class CustomView extends View {
    private Paint mPaint;
    public CustomView(Context context) {
        this(context,null);
    }

    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
        // 设置shader
        BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        mPaint.setShader(shader);
        // 用设置好的画笔绘制一个矩形
        mPaint.setColor(Color.GREEN);
        canvas.drawRect(0, 0, 1000, 1000, mPaint);
        mPaint.reset();
    }
}

我x轴和y轴都是重复这张图片的,效果如下:

我现在把x轴改为MIRROR也就是镜像,y轴不变,

这就是x轴镜像y轴重复这个图片的效果,现在就剩下一个模式没讲就是CLAMP,就是边缘像素拉伸,

为了演示这个效果,要换一张图片,

我现在把x轴改为REPEAT,y轴改为CLAMP,效果如下:

如果x,y轴都是clamp模式也就是边缘拉伸,如下

我现在再把图片边缘颜色改下,这下总可以知道这个模式是干嘛的吧,‘

x,y轴方向都CLAMP模式,效果如下:

这个黑色没显示出来,是因为我没画好,好了,这个模式我相信我已经讲的很清楚了,为了弄清这个花了很久的时间去想怎么写出来才看的懂这个模式,

LinearGradient

这个是线性渐变颜色渲染器

它有2个构造函数

public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,TileMode tile)

参数说明:

x0: 渐变起始点x坐标

y0:渐变起始点y坐标

x1:渐变结束点x坐标

y1:渐变结束点y坐标

color0: 起始渐变色

color1: 结束渐变色

tile: 渲染器平铺模式

这就是2个点起始点为(x0,y0)终点为(x1,y1) 起始点的颜色为color0,终点的颜色为color1

现在写个例子再理解上面几个坐标

public class CustomImageView extends View {
    private Paint mPaint;
    private LinearGradient linearGradient,linearGradient1,linearGradient2;
    public CustomImageView(Context context) {
        this(context, null);
    }

    public CustomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        linearGradient = new LinearGradient(0, 0, 100, 100,Color.RED , Color.YELLOW,
                Shader.TileMode.REPEAT);
        linearGradient1 = new LinearGradient(0, 0, 0, 100,Color.RED , Color.YELLOW,
                Shader.TileMode.REPEAT);

        linearGradient2= new LinearGradient(0, 0, 100, 0,Color.RED , Color.YELLOW,
                Shader.TileMode.REPEAT);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置渲染器
        mPaint.setShader(linearGradient);
        canvas.drawColor(Color.BLUE);
        canvas.drawRect(0, 0, 300, 300, mPaint);
        mPaint.setShader(linearGradient1);
        canvas.drawRect(0, 350, 300, 650, mPaint);
        mPaint.setShader(linearGradient2);
        canvas.drawRect(0, 700, 300, 1000, mPaint);
    }
}

效果:

看下第二个和第三个LinearGradient构造函数中给的值

linearGradient1 = new LinearGradient(0, 0, 0, 100,Color.RED , Color.YELLOW, Shader.TileMode.REPEAT);
linearGradient2= new LinearGradient(0, 0, 100, 0,Color.RED , Color.YELLOW, Shader.TileMode.REPEAT);

分析图:

分析图二:

关于它的最好一个参数模式在讲BitmapShader已经讲的很清楚了,不在这多讲,

RadialGradient
环形

首先看下它的构造函数,

public RadialGradient(float centerX, float centerY, float radius,int centerColor, int edgeColor, TileMode tileMode)

参数说明:

centerX:圆心的x轴坐标

centerY:圆心的y轴坐标

radius:圆的半径

centerColor:圆心的颜色

edgeColor:圆心边缘的颜色

tileMode:平铺模式

public class CustomView extends View {
    private Paint mPaint;
    private RadialGradient mRadialGradient;
    public CustomView(Context context) {
        this(context,null);
    }
    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        //1.圆心X坐标2.Y坐标3.半径 4.颜色数组 5.相对位置数组,可为null 6.渲染器平铺模式
        mRadialGradient = new RadialGradient(240, 360, 60, Color.RED, Color.YELLOW,
                Shader.TileMode.REPEAT);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.GRAY);
        mPaint.setShader(mRadialGradient);
        mPaint.setAntiAlias(true);
        canvas.drawCircle(240, 360, 200, mPaint);
    }
}

效果图:

通过这个可以做一个垃圾版的水波纹效果,

代码如下:

public class CustomView extends View {
    private Paint mPaint;
    private Handler mHandler;
    private int radius = 20;
    private int tag = 100001;
    private RadialGradient mRadialGradient;
    public CustomView(Context context) {
        this(context,null);
    }
    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what==tag){
                    if(radius>200){
                        radius=20;
                    }else{
                        radius+=5;
                    }
                    invalidate();
                }
            }
        };
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.GRAY);
        mRadialGradient = new RadialGradient(240, 360, radius, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT);
        mPaint.setShader(mRadialGradient);
        canvas.drawCircle(240, 360, 200, mPaint);
        mHandler.sendEmptyMessageAtTime(tag,300);
    }
}

RadialGradient还有一个构造函数:

public RadialGradient(float centerX, float centerY, float radius,int colors[], float stops[], TileMode tileMode)

构造函数

centerX:圆心的x轴坐标

cneterY:圆心的y轴坐标

radius:圆的半径

colors:int[] 数组其实就是多个颜色值的集合

stops:该数组中每一个stop对应colors数组中每个颜色在半径中的相对位置,stop取值范围为[0,1],0表示圆心位置,1表示圆周位置。如果stops数组为null,那么Android会自动为colors设置等间距的位置

tileMode:模式

好了 这个有时间再把剩余2个补上,不然这个博客还不知道什么时候写完,现在讲paint另一个很重要的方法

mPaint.setXfermode(Xfermode);

Xfermode就是图形混合模式,它有三个子类

关于这个知识点一时也讲不清楚,准备另外写一篇博客讲解下,在这里贴下代码,自己可以去百度下,

/**
 * 合成图片
 * @param resId 图片 id
 */
public Bitmap compoundBitmap(int resId){
    Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(),resId);//把资源图片变成一个Bitmap对象
    //生成下面的一半图片
    Matrix matrix = new Matrix();
    matrix.setScale(1,-1);
    Bitmap invertBitmap = Bitmap.createBitmap(originalBitmap,0,originalBitmap.getHeight()/2,originalBitmap.getWidth(),originalBitmap.getHeight()/2,matrix,false);
    //创建一个空的位图
    Bitmap compoundBitmap =  Bitmap.createBitmap(originalBitmap.getWidth(),originalBitmap.getHeight()+invertBitmap.getHeight()+10, Bitmap.Config.ARGB_8888);//+10是为了2张图片之间有空隙

    Canvas canvas = new Canvas(compoundBitmap);
    canvas.drawBitmap(originalBitmap,0,0,null);
    canvas.drawBitmap(invertBitmap,0,originalBitmap.getHeight()+10,null);
    Paint paint = new Paint();
    // 设置渐变颜色
    LinearGradient shader = new LinearGradient(0, originalBitmap.getHeight() + 10, 0, compoundBitmap.getHeight(), 0x70ffffff, 0x00ffffff, Shader.TileMode.CLAMP);
    paint.setShader(shader);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawRect(0, originalBitmap.getHeight() + 5, originalBitmap.getWidth(), compoundBitmap.getHeight(), paint);
    return  compoundBitmap;
}

渐变颜色染色器上面是讲了,现在看下效果,

ok,现在完成了倒影的效果,

public class MainActivity extends Activity {
    private Gallery gallery;
    private MyAdapter adapter;
    private int[] ids ={R.mipmap.a,R.mipmap.b,R.mipmap.c,R.mipmap.d,R.mipmap.e,R.mipmap.f,R.mipmap.g,R.mipmap.h,R.mipmap.i};
    private int screenWidth;
    private int screenHeigh;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getScreenWidthAndHeight();
        gallery = (Gallery) findViewById(R.id.gallery);
        adapter = new MyAdapter();
        gallery.setAdapter(adapter);
    }
    class MyAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return ids.length;
        }
        @Override
        public Object getItem(int position) {
            return null;
        }
        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return 0;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView iv = null;
            if(convertView == null) {
                iv = new ImageView(MainActivity.this);
            } else {
                iv = (ImageView) convertView;
            }
            Bitmap bitmap = Utils.compoundBitmap(
                    MainActivity.this.getResources(), ids[position]);
            BitmapDrawable bd = new BitmapDrawable(bitmap);
            bd.setAntiAlias(true);    // 消除锯齿
            iv.setImageDrawable(bd);
            Gallery.LayoutParams params = new Gallery.LayoutParams(screenWidth/2, screenHeigh/2);
            iv.setLayoutParams(params);
            return iv;
        }
    }

    /**
     * 获取屏幕的宽和高
     */
    public void getScreenWidthAndHeight(){
            DisplayMetrics dm = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(dm);
            screenWidth = dm.widthPixels;
            screenHeigh = dm.heightPixels;
    }
}

效果:

现在就是计算滑动时候旋转的角度问题了,看下原型图

发现这有几个效果

1:中间的图片明显比二边的图片要大,

2:中间的图片没有旋转,二边的图片还有旋转

3:在滑动过程中还有二边的图片还有透明的效果

尼玛  鬼做的出来,经过百度再百度,找到一些别人实现好的方法,懂了原理,原来是这样的,首先给别人点赞,太牛逼

这个控件我们是自定义Gallery来实现的,

public class MyGallery extends Gallery {
    public MyGallery(Context context) {
        super(context);
    }

    public MyGallery(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyGallery(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected boolean getChildStaticTransformation(View child, Transformation t) {
        return super.getChildStaticTransformation(child, t);
    }
}

发现什么也没写,对吧 ,是的 在思考,

上面自定义的gallery中重写了protected boolean getChildStaticTransformation(View child, Transformation t)方法,这是表示gallery在滑动过程中返回它item的子视图的效果,

参数说明:

child:就是你gallery中滑动的veiw

t:就是当前item的变化效果,

现在我在adapter中的getView()方法中给每个imageview设置下tag,tag的值就是当前的position,当我们运行起来什么都没干的时候,getChildStaticTransformation()方法打印出来的log

05-26 03:23:16.931 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:16.931 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:16.941 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->1

05-26 03:23:16.968 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:16.968 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:16.968 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:16.968 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->1

05-26 03:23:16.968 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:25.644 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->0

05-26 03:23:25.644 32525-32525/com.example.customgallery E/MyGalleryHAHA: tag--->1

05-26 03:23:59.180 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:23:59.180 755-755/? E/MyGalleryHAHA: tag--->

05-26 03:23:59.189 755-755/? E/MyGalleryHAHA: tag--->1

05-26 03:23:59.204 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:23:59.204 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:23:59.204 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:23:59.204 755-755/? E/MyGalleryHAHA: tag--->1

05-26 03:23:59.204 755-755/? E/MyGalleryHAHA: tag--->0

进来就发现只显示2个item,当我往右滑动的一个item的时候,也就是说第二个item出现的时候,log

05-26 03:26:25.186 755-755/? E/MyGalleryHAHA: tag--->2

05-26 03:26:25.187 755-755/? E/MyGalleryHAHA: tag--->2

05-26 03:26:25.678 755-755/? E/MyGalleryHAHA: tag--->1

05-26 03:26:25.678 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:26:25.682 755-755/? E/MyGalleryHAHA: tag--->0

05-26 03:26:25.682 755-755/? E/MyGalleryHAHA: tag--->1

发现是刚滑动出来的先打印出来,其他没发现有什么规律,

旋转的计算分析图:

gallery中的代码全部如下:

package com.example.customgallery;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
import android.widget.ImageView;

/**
 * Created by admin on 2016/5/26.
 */
public class MyGallery extends Gallery {
    private int centerPoint;
    private static final String TAG = "MyGalleryHAHA";
    private Camera mCamera;
    private int  maxRoate = 60;//旋转的最大角度
    public MyGallery(Context context) {
        super(context);
        mCamera = new Camera();
        setStaticTransformationsEnabled(true);
    }
    public MyGallery(Context context, AttributeSet attrs) {
        super(context, attrs);
        mCamera = new Camera();
        setStaticTransformationsEnabled(true);
    }
    public MyGallery(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mCamera = new Camera();
        setStaticTransformationsEnabled(true);
    }

    @Override
    protected boolean getChildStaticTransformation(View child, Transformation t) {
        int viewCenterPoint = getItemViewCenterPoint(child);   // item的中心点
        int rotateAngle = 0;      // 默认旋转角度为0
        // 如果当前的View的中心点不等于gallery的中心点, 就是两边的图片, 需要计算旋转角度
        if(viewCenterPoint != centerPoint) {
            // gallery中心点 - 图片中心点 = 差值
            int diff = centerPoint - viewCenterPoint;
            // 差值 / 图片的宽度 = 比值
            float scale = (float)diff / (float)child.getWidth();
            // 比值 * 最大旋转角度 = 最终的旋转角度
            rotateAngle = (int) (scale * maxRoate);
            if(Math.abs(rotateAngle) > maxRoate) {    // 当前角度超过了50, 需要赋值到50 或者 -50
                rotateAngle = rotateAngle > 0 ? maxRoate : -maxRoate;
            }
        }
        // 设置变换效果之前, 需要把Transformation中的上一个item的变换效果清楚
        t.clear();
        t.setTransformationType(Transformation.TYPE_MATRIX);   // 设置变换效果的类型为矩阵类型
        setItemStartAnim((ImageView) child, rotateAngle, t);
        return true;
    }
    /**
     * 设置变换效果
     * @param iv galleryitem
     * @param rotateAngle 旋转的角度
     * @param t 变换的对象
     */
    private void setItemStartAnim(ImageView iv, int rotateAngle, Transformation t) {
        mCamera.save();       // 保存状态

        int absRotateAngle = Math.abs(rotateAngle);    // 取旋转角度的绝对值

        // 放大效果
        mCamera.translate(0, 0, 100f);    // 给摄像机定位

        int zoom = -240 +(absRotateAngle * 2);
        mCamera.translate(0, 0, zoom);

        // 透明度(中间的图片是完全显示, 两边有一定的透明度)
        int alpha = (int) (255 - (absRotateAngle * 2.5));
        iv.setAlpha(alpha);       // 透明度取值范围: 0 ~ 255, 0 就是完全隐藏, 255 完全显示

        // 旋转(在中间的图片没有旋转角度, 只要不在中间就有旋转角度)
        mCamera.rotateY(rotateAngle);

        Matrix matrix = t.getMatrix();     // 变换的矩阵, 需要把变换的效果添加到矩阵中

        // 给matrix赋值
        mCamera.getMatrix(matrix);    // 把matrix矩阵给camera对象, camera对象就会把上面添加的效果转换成矩阵添加到matrix对象中

        // 矩阵前乘
        matrix.preTranslate(-iv.getWidth() / 2, -iv.getHeight() / 2);

        // 矩阵后乘
        matrix.postTranslate(iv.getWidth() / 2, iv.getHeight() / 2);

        mCamera.restore(); // 恢复到之前保存的状态
    }
    /**
     * 获取gallery的中心点
     * @return
     */
    public int getCenterPoint(){
        return getWidth()/2;
    }
    /**
     * 获取item  view的中心点
     */
    public int getItemViewCenterPoint(View itemView) {
        if (itemView != null) {
            return itemView.getWidth() / 2 + itemView.getLeft();
        }
        return  0;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerPoint = getCenterPoint();
    }
}

在这要注意点:

getChildStaticTransformation()方法的返回值一定要返回true,否则没效果,

最终的效果如下:

写了2天多了,终于写完了!也许写的不够好,但真的尽力了!

时间: 2024-10-11 18:07:28

android 自定义控件实现3D画廊效果的相关文章

利用ViewPager实现3D画廊效果及其图片加载优化

前言 对于ViewPager,相信大家都已经很熟悉了,在各种切换场景比如Fragment切换.选项卡的切换或者顶部轮播图片等都可以用ViewPager去实现.那么本篇文章带来ViewPager的一种实现效果:3D画廊.直接上图来看: 从上面的图我们可以看出,整个页面分成三个部分,中间的是大图,正中地显示给用户:而两边的是侧图,而这两幅图片又有着角度的旋转,与大图看起来不在同一平面上,这就形成了3D效果.接着拖动页面,侧面的图慢慢移到中间,这个过程也是有着动画的,包括了图片的旋转.缩放和平移.在欣

Android ViewPager打造3D画廊

网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This class was deprecated in API level 16.This widget is no longer supported. Other horizontally scrolling widgets include HorizontalScrollView and ViewPager from the support library.(来自:https://develo

Android -- 使用ViewPager实现画廊效果

1,今天在微信推送文章看到实现画廊效果,感觉挺不错的,就来写写试试,先来看一下效果图: 上面的效果基本上可以用两个功能点来包含:ViewPager的切换动画.ImageView的倒影的实现 嗯,先来将我们主要的功能来实现,就是我们的ViewPager展示图片 主界面的布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.

Android自定义控件练手——波浪效果

这一次要绘制出波浪效果,也是小白的我第一次还望轻喷.首先当然是展示效果图啦: 一.首先来说说实现思路. 想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在demo里这种方法也实现了),肯定要绘制一个静态的波,然后通过不断的对它平移刷新,这样最简单的波浪效果就有了,如果再给它加一个比它提前一定周期的波一起平移,那不是波浪效果的层次就有了. 二.绘制. 首先要绘制一个静态的波形图,嗨呀说来简单但是怎么画呢,不要慌先看下面这张丑图: 通过上面的图我们发现曲

android 自定义控件之 tab滑动效果

下午没有白费,终于仿照别人的代码做出来了. 大体思路是这样的 下面上代码 http://yunpan.cn/Qa5i6s9ApvrSt 提取码 79be

Android自定义控件——3D画廊和图像矩阵

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39932689 1.3D画廊的实现 我们知道android系统已经为我们提供好了一个展示图片的"容器"--Gallery,但是这个Gallery显示的效果是平面化的,动态效果不强.这里,我们动手做一个自定义的Gallery组件,实现图片的3D效果展示,想想应该不错吧,先看看效果图: 实现这个3D效果的Gallery该怎么做呢?首先,分析一下, 1,展示图片,系统自带Gal

Android自定义控件 -Canvas绘制折线图(实现动态报表效果)

有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了很多插件,但是很多时候我们需要根据具体项目自定义这些图表,这一篇文章我们一起来看看如何在Android中使用Canvas绘制折线图.先看看绘制的效果: 代码: public class MyView extends View { //坐标轴原点的位置 private int xPoint=60; private int yPoint=260; //刻度长度 private int xScale=8;  //8个单位构

[转]Android自定义控件系列五:自定义绚丽水波纹效果

出处:http://www.2cto.com/kf/201411/353169.html 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二:自定义开关按钮(一)).观察这个效果,发现应该

【Android自定义控件】圆圈交替,仿progress效果

还是我们自定View的那几个步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure (不是必须) 4.重写onDraw 自定义View的属性 <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="firstColor" format="color" /> <att