Bitmap Shader of Android Drawing Shader

Keywords: Android xml encoding

Shader

Shader is called a shader in the field of computer graphics. It is a set of drawing instructions provided to GPU to tell GPU how to draw and render the objects when drawing.

Android defines several Shaders for Paint to use. When Paint draws an image, it sets different Shaders, and the drawn objects are colored with the information provided by Shader.

The subclasses of Shader are: BitmapShader, LinearGradient, ComposeShader, Radial Gradient, SweepGradient

BitmapShader

BitmapShader uses a specified image to color Paint. When drawing, different effects will be created according to the TileMode and image set. TileMode has

There are three types:

  • In CLAMP mode, if the drawing area exceeds the size of the image provided, the outside area will be colored with the edge color of the outside part.

  • In REPEAT mode, if the drawing area exceeds the size of the image provided, the beyond area will be re-colored with the complete picture.

  • In MIRROR mode, if the drawing area exceeds the size of the picture provided, the area beyond will be re-colored with the full picture and will have a mirror reversal effect.

BitmapShader constructor:

/**
 * The only constructor that is called to construct a new BitmapShader
 *
 * @param bitmap            bitmap objects for coloring
 * @param tileX             Horizontal tiling mode.
 * @param tileY             Vertical tiling mode.
 */
public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) {

An example of BitmapShader


public class BitmapShaderView extends View {

    private Paint mPaint;

    private Shader mShader;

    /**
     * Pictures used to colour 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();
        //Drawing using Paint with Shader set
        canvas.drawRect(0,0,width,height,mPaint);
    }


    /**
     * Reset BitmapShader
     * @param tileX
     * @param tileY
     */
    public void resetShader(Shader.TileMode tileX,Shader.TileMode tileY){
        mShader = new BitmapShader(mBitmap, tileX, tileY);
        //Call Paint's setShader(Shader shader) method to set BitmapShader
        mPaint.setShader(mShader);
        invalidate();
    }




}

In the code, a public method resetShader is provided to set up different tiling modes, so as to achieve dynamic performance.

Layout code:

<?xml version="1.0" encoding="utf-8"?>
<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 code:

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);
    }
}

Switch different tiling modes by clicking different buttons

CLAMP rendering:

REPEAT effect map:

MIRROR rendering:

Application of BitmapShader

BitmapShader will still be used in practical development, such as implementing circular ImageView and rounded corner ImageView.

The following effects and codes are for demonstration purposes only, and other detailed factors and conditions need to be considered when applying them to the project.

Implementation of Round Image View and Round corner Image View by BitmapShader

Design sketch:

Code:


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);
        /*
         *If it's a circle, you need to make the width ratio of the View the same.
         */
        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 it is a circle, draw a circular picture, otherwise draw a rounded rectangle.
         */
        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);

        }


    }




}

Custom properties:

<?xml version="1.0" encoding="utf-8"?>
<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>

Layout code:

<LinearLayout
    android:layout_width="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" />


</LinearLayout>

Default Style:

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="roundImageViewStyle">@style/RoundImageViewStyle.Default</item>
</style>

<style name="RoundImageViewStyle.Default" parent="@android:style/Widget">
    <item name="roundType">circular</item>
</style>

Posted by Mortier on Thu, 23 May 2019 14:16:54 -0700