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>