当前位置 博文首页 > 启舰:自定义控件三部曲之绘图篇(十八)——BitmapShader与望远

    启舰:自定义控件三部曲之绘图篇(十八)——BitmapShader与望远

    作者:[db:作者] 时间:2021-06-30 15:32

    前言:不逼自己一把,你永远不知道自己有多优秀。

    系列文章:

    Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268

    上篇初步给大家展示了封装控件的方法,这篇我们继续Paint来看相关方法的用法,这篇我们将会讲一个很起来没啥用,但效果却很屌的方法setShader,这篇文章最后,我们将实现的效果是望远镜效果:(看起来有没有屌屌的)
    在这里插入图片描述
    我们先来看看setShader函数的声明:

    //Paint类中的方法
    public Shader setShader(Shader shader)
    

    Shader在三维软件中称之为着色器,就是用来给空白图形上色用的。在PhotoShop中有一个工具叫印章工具,我们能够指定印章的样式来填充图形。印章的样式可以是图像、颜色、渐变色等。这里的Shader实现的效果与印章类似。我们也是通过给Shader指定对应的图像、渐变色等来填充图形的。
    Shader类只是一个基类,它其中只有两个方法setLocalMatrix(Matrix localM)、getLocalMatrix(Matrix localM)用来设置坐标变换矩阵的,有关设置矩阵的内容,我们后面会单独讲解坐标矩阵用法的时候,会再次提,这里就先略过。
    Shader类与ColorFiler一样,其实是一个空类,它的功能的实现,主要是靠它的派生类来实现的。继承关系如下:
    在这里插入图片描述
    下面我们就来逐个来看每个派生类的用法与效果。
    ###一、BitmapShader基本用法
    ####1、概述
    我们这篇文章只看一个派生类:BitmapShader,它的构造函数如下:

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

    这个就相当于PhotoShop中的图案印章工具,bitmap用来指定图案,tileX用来指定当X轴超出单个图片大小时时所使用的重复策略,同样tileY用于指定当Y轴超出单个图片大小时时所使用的重复策略
    其中TileMode的取值有:

    • TileMode.CLAMP:用边缘色彩填充多余空间
    • TileMode.REPEAT:重复原图像来填充多余空间
    • TileMode.MIRROR:重复使用镜像模式的图像来填充多余空间

    只看这些还是啥都不懂,我们先来举个例子来看下用法
    ####2、BitmapShader使用示例
    这里使用的印章图像是:(dog_edge.png)
    在这里插入图片描述
    中间是我们熟悉的小狗,四周被四种不同的颜色给包围,这些颜色是我特地画上去的,后面自然有它的用处。
    我们还是先直接来看完整代码吧:

    public class BitmapShaderView extends View {
        private Paint mPaint;
        private Bitmap mBmp;
        public BitmapShaderView(Context context) {
            super(context);
            init();
        }
    
        public BitmapShaderView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public BitmapShaderView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        private void init(){
            mPaint = new Paint();
            mBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog_edge);
            mPaint.setShader(new BitmapShader(mBmp, TileMode.REPEAT, TileMode.REPEAT));
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //getWidth()用于获取控件宽度,getHeight()用于获取控件高度
            canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
        }
    }
    

    代码其实很简单,在初始化的时候设置印章图片:

    private void init(){
        mPaint = new Paint();
        mBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog_edge);
        mPaint.setShader(new BitmapShader(mBmp, TileMode.REPEAT, TileMode.REPEAT));
    }
    

    然后在绘图的时候,利用paint绘制一个矩形,这个矩形的大小与控件的大小一模一样:

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //getWidth()用于获取控件宽度,getHeight()用于获取控件高度
        canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
    }
    

    然后在布局中使用时:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent">
        <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="test BTN"/>
    
        <com.harvic.Blog_BitmapShader.BitmapShaderView
                android:layout_width="200dp"
                android:layout_height="400dp"
                android:layout_gravity="center_horizontal"/>
    </LinearLayout>
    

    给我们自定义的控件添加上宽高限制,为了方便看效果,我在它上面也另外加了一个按钮
    效果图如下:
    在这里插入图片描述
    从效果图中可以看出:

    • 使用X轴和Y轴都使用REPEAT模式下,在超出单个图像的区域后,就会重复绘制这个图像
    • 绘制是从控件的左上角开始的,而不是从屏幕原点开始的!这点很好理解,因为我们绘图也只会在自定义控件上绘图,不会在全屏幕上绘图。

    ####3、TileMode模式解析
    上面初步看到了REPEAT模式的用法,现在我们分别来看在各个模式下的不同表现
    (1)、TileMode.REPEAT模式:重复原图像来填充多余空间
    在更改模式时,只需要更新setShader里的代码:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.REPEAT, TileMode.REPEAT));
    

    在这里,X轴、Y轴全部设置成REPEAT模式,所以当控件的显示范围超出了单个图的显示范围时,在X轴上将使用REPEAT模式,同样,在Y轴上也将使用REPEAT模式
    效果图如下:
    在这里插入图片描述
    (2)、TileMode.MIRROR模式:重复使用镜像模式的图像来填充多余空间
    同样,将X轴、Y轴全部改为MIRROR模式:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.MIRROR, TileMode.MIRROR));
    

    效果图如下:
    在这里插入图片描述
    先看效果图的X轴:在X轴上每两张图片的显示都像镜子一样翻转一下。
    同样,在Y轴上每两张图片的显示也都像镜子一样翻转一下。
    所以这就是镜相效果的作用,镜相效果其实就是在显示下一图片的时候,就相当于两张图片中间放了一个镜子一样。
    (3)、TileMode.CLAMP:用边缘色彩填充多余空间
    同样,我们还是将X轴、Y轴全部改为CLAMP模式:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.CLAMP, TileMode.CLAMP));
    

    效果图如下:
    在这里插入图片描述
    CLAMP模式的意思就是当控件区域超过当前单个图片的大小时,空白位置的颜色填充就用图片的边缘颜色来填充。
    (4)、TileMode.CLAMP与填充顺序
    我们还是先来看一下原图像:
    在这里插入图片描述
    按照我们上面讲的,当X轴、Y轴全部都是CLAMP模式时,X轴的空白区域会用图像的右侧边缘颜色来填充;Y轴的空白区域会用图像的底部的边缘颜色来填充,那效果应该是这样的:
    在这里插入图片描述
    明显右下角的空白位置根本与图像是不沾边的,那它要用什么颜色来填充呢?是填充上方的蓝色还是填充左侧的绿色呢?
    从最终的效果图来看,这部分填充的颜色是绿色的,可为什么呢?
    其实这是跟填充顺序有关的,因为我们同时要填充横向和竖向;那到底是先填充横向还是先填充竖向呢?
    答案是先填充竖向!在填充竖向后的结果如下:
    在这里插入图片描述
    在填充竖向后,整个竖向都是有颜色的了,此时再根据竖向的边缘色彩来填充横向:
    在这里插入图片描述
    红色方框的区域就是根据竖向的边缘色彩来填充的,这样,当X轴Y轴全是CLAMP时,就理解为什么右下角是填充的绿色而不是蓝色的原因了。
    (5)、当MIRROR与REPEAT混用时
    TileMode.MIRROR, TileMode.REPEAT
    上面我们在填充X轴 和Y轴的空白位置时,都是用的同一种模式,下面我们就来看一下当X轴与Y轴的填充模式不一样时,效果又是怎样的呢?
    这里我们假设X轴填充空白区域时,使用MIRROR样式、在填充Y轴空白区域时,使用REPEAT样式:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.MIRROR, TileMode.REPEAT));
    

    效果图如下:
    在这里插入图片描述
    无论哪两种模式混合,我们在理解时只需要记着填充顺序是先填充Y轴,然后再填充X轴!这样效果图就很好理解了
    首先,是先填充Y轴,在填充Y轴时使用的是REPEAT模式,此时的效果图是:
    在这里插入图片描述
    在填充Y轴以后再利用X轴的镜相模式来填充X轴,这样整个控件就被填充完毕了。
    TileMode.REPEAT,TileMode.MIRROR,
    下面我们再反过来看一下当X轴使用REPEAT模式,Y轴使用MIRROR模式效果会怎样:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.REPEAT, TileMode.MIRROR));
    

    效果图如下:
    在这里插入图片描述
    同样是先使用镜相模式来填充Y轴,然后再使用REPEAT模式来填充X轴;所以从效果图中可以明显看出第一列的Y轴全部是镜相效果。然后再根据第一列的镜相效果来填充X轴,由于X轴使用的是REPEAT模式,所以X轴的图像全部都与左侧第一列的图像相同。
    (6)、CLAMP模式与其它模式混用
    上面我们理解了填充顺序的意义以后,下面再来看一下最难的两种混用方式,就是当CLAMP模式与其它模式混用时的效果。
    比如,当X轴使用CLAMP效果填充,而Y轴使用MIRROR效果填充时:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.CLAMP, TileMode.MIRROR));
    

    效果图如下:
    在这里插入图片描述
    从效果图中很好理解,先填充Y轴,填充以后的Y轴各个图像是镜相分布的。而此时再使用CLAMP模式来填充X轴,会拿Y轴图像最边缘的颜色来进行填充。理解难度不大,就不再细讲了。
    下面再将这两种模式反过来,X轴使用MIRROR模式而Y轴使用CLAMP模式:

    mPaint.setShader(new BitmapShader(mBmp, TileMode.MIRROR, TileMode.CLAMP));
    

    效果图如下:
    在这里插入图片描述
    想必大家看到效果图以后,也理解为什么会出现这种效果了,这里就不再讲了,如果还不懂,把上面讲的再看一遍。
    ####4、绘图位置与模式的关系
    在上面的例子中,我们利用drawRect把整个控件大小都给覆盖了,那假如我们只画一个小矩形而不完全覆盖整个控件,那我们SetShader的图片是从哪里开始画的呢?
    是从开始drawRect所绘矩形的左上角开始画,还是在控件的左上角开始的呢?
    我们举个例子来看下:

    public class BitmapShaderView extends View {
        private Paint mPaint;
        private Bitmap mBmp;
        public BitmapShaderView(Context context) {
            super(context);
            init();
        }
    
        public BitmapShaderView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public BitmapShaderView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        private void init(){
            mPaint = new Paint();
            mBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog_edge);
            mPaint.setShader(new BitmapShader(mBmp, TileMode.MIRROR, TileMode.