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

Posted zhoushenxian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.handleMessag

以上是关于android 自定义控件实现3D画廊效果的主要内容,如果未能解决你的问题,请参考以下文章

android画廊怎样做出超炫效果

android画廊怎样做出超炫效果

Android自定义露一手,轻松实现View 3D 翻转效果

Android打造万能自定义阴影控件

自定义View 之利用ViewPager 实现画廊效果(滑动放大缩小)

自定义View 之利用ViewPager 实现画廊效果(滑动放大缩小)