Android 绘图Shader之BitmapShader



Shader

Shader在计算机图形领域叫做着色器,是一组提供给GPU的绘图指令,用于告诉GPU在绘图时应该怎么绘制并对绘制的物体进行色彩渲染。

Android中定义了几种Shader给Paint使用,在Paint绘制图像时对其设置不同的Shader,绘制出来的物体就会使用Shader提供的信息进行着色。

Shader的子类有:BitmapShader、LinearGradient、ComposeShader、RadialGradient、SweepGradient

BitmapShader

BitmapShader是使用一张指定的图片给Paint进行着色,在绘制的时候会根据设置的TileMode(平铺模式)和图像来形成不同的效果,其中TileMode有

如下三种:

  • CLAMP 这种模式在绘制的时候如果绘制区域超出提供的图片的尺寸,超出区域会使用超出部分的边缘颜色进行着色

  • REPEAT 这种模式在绘制的时候如果绘制区域超出提供的图片的尺寸,超出区域会重新使用完整的图片进行着色

  • MIRROR 这种模式在绘制的时候如果绘制区域超出提供的图片的尺寸,超出区域会重新使用完整的图片进行着色,并且呈镜面反转效果

BitmapShader构造函数:


/** * 唯一的一个构造函数,调用这个构造函数构造一个新的BitmapShader * * @param bitmap 用于着色的bitmap对象 * @param tileX 水平方向的平铺模式. * @param tileY 垂直方向的平铺模式. */
public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) {

BitmapShader实例



public class BitmapShaderView extends View { private Paint mPaint; private Shader mShader; /** * 用于给Paint着色的图片 */ private Bitmap mBitmap; public BitmapShaderView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initial(); } private void initial(){ mPaint = new Paint(); mPaint.setAntiAlias(true); mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.sunwukong); resetShader(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); //使用设置了Shader的Paint进行绘制 canvas.drawRect(0,0,width,height,mPaint); } /** * 重新设置BitmapShader * @param tileX * @param tileY */ public void resetShader(Shader.TileMode tileX,Shader.TileMode tileY){ mShader = new BitmapShader(mBitmap, tileX, tileY); //调用Paint的setShader(Shader shader)方法设置BitmapShader mPaint.setShader(mShader); invalidate(); } } 

在代码中提供了一个public方法resetShader来设置不同的平铺模式,这样可以达到动态变化的演示效果。

布局代码:



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" >
<com.example.debugm.BitmapShaderView  android:id="@+id/shader_view" android:layout_width="match_parent" android:layout_height="100dp" />

    <Button  android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="clamp" android:text="@string/bitmap_shader_clamp" />

    <Button  android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="mirror" android:text="@string/bitmap_shader_mirror" />

    <Button  android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="repeat" android:text="@string/bitmap_shader_repeat" />

LinearLayout>

Activity代码:


public class MainActivity extends AppCompatActivity {

    private BitmapShaderView mShaderView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mShaderView = (BitmapShaderView)findViewById(R.id.shader_view);
    }


    public void clamp(View view){
        mShaderView.resetShader(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    }

    public void repeat(View view){
        mShaderView.resetShader(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    }

    public void mirror(View view){
        mShaderView.resetShader(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
    }
}

通过点击不同的按钮切换不同的平铺模式

CLAMP效果图:

这里写图片描述

REPEAT效果图:

这里写图片描述

MIRROR效果图:

这里写图片描述

BitmapShader实际应用

BitmapShader在实际开发中还是会用到的,比如实现圆形ImageView,圆角ImageView

以下效果和代码只做演示用,具体使用到项目中还需要考虑其他详细的因素和条件

BitmapShader实现圆形ImageView和圆角ImageView

效果图:

这里写图片描述

代码:



public class RoundImageView extends ImageView { private static final int RECTANGLE = 0; private static final int CIRCULAR = 1; private static final float DEFAULT_RADIUS = 50f; private float mRadius = DEFAULT_RADIUS; private int mRoundType = CIRCULAR; private Paint mPaint; private Bitmap mBitmap; private Shader mShader; public RoundImageView(Context context) { this(context,null); } public RoundImageView(Context context,AttributeSet attrs) { this(context, attrs,R.attr.roundImageViewStyle); } public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.RoundImageView, defStyleAttr, R.style.RoundImageViewStyle_Default); mRadius = a.getDimension(R.styleable.RoundImageView_android_radius,DEFAULT_RADIUS); mRoundType = a.getInt(R.styleable.RoundImageView_roundType, CIRCULAR); a.recycle(); initialShader(); } private void initialShader(){ mPaint = new Paint(); mPaint.setAntiAlias(true); mBitmap = BitmapUtils.drawable2bitmap(getDrawable()); if(mBitmap != null){ mShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(mShader); } } private boolean isCircular(){ return mRoundType == CIRCULAR; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /* *如果是圆形的话需要让View的宽高比例一样 */ if(isCircular()){ int width = Math.min(getMeasuredWidth(),getMeasuredHeight()); int height = Math.min(width,getMeasuredHeight()); setMeasuredDimension(width,height); } } @Override protected void onDraw(Canvas canvas) { if(getDrawable() == null){ return; } int width = getWidth(); int height = getHeight(); float radius; /* *如果是圆形则绘制圆形图片,否则绘制圆角矩形 */ if(isCircular()){ radius = width/2; canvas.drawCircle(width / 2,height / 2,radius,mPaint); }else{ radius = mRadius; canvas.drawRoundRect(0f,0f,width*1.0f,height*1.0f,radius,radius,mPaint); } } } 

自定义属性:



<resources>


    <declare-styleable name="RoundImageView">
        <attr name="android:radius" />
        <attr name="roundType" format="enum">
            <enum name="rectangle" value="0"/>
            <enum name="circular" value="1"/>
        attr>
    declare-styleable>

    <attr name="roundImageViewStyle" format="reference" />

resources>

布局代码:


"match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:showDividers="middle"
    android:divider="@drawable/divider_vertical"
    >

    <com.example.debugm.RoundImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/sunwukong" />


    <com.example.debugm.RoundImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:roundType="rectangle"
        android:radius="5dp"
        android:src="@drawable/sunwukong2" />




默认样式:


-- Base application theme. -->