Android Shader渲染以及實現水波紋霓虹文字雷達等效果
Shader概述
Shader是繪圖過程中的著色器,實現繪製各種不同的效果,比如映象,水波紋,雷達等等,Shader有以下五個子類:
- BitmapShader用於Bitmap圖片的渲染
- ComposeShader用於混合渲染
- LinearGradient用於線性渲染
- RadialGradient用於環形渲染
- SweepGradient用於梯度渲染
Shader的三種模式TileMode
- CLAMP 當繪製的區域超過了原始的大小,超出的區域就會用邊緣的顏色進行拉伸
- REPEAT 重複水平或者豎直方向的圖片
- MIRROR 用圖片的映象填充
BitmapShader
構造方法
BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
- bitmap:需要著色的點陣圖
- tileX:X方向的填充模式
- tileY:Y方向的填充模式
例項
這是一個充值後的影魔,直接看看程式碼的實現:
public class ShaderView extends View {
Bitmap mBitmap;
BitmapShader mBitmapShader;
Paint mPaint;
int mWidth;
int mHeight;
public ShaderView(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);
}
public ShaderView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(mBitmapShader);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
}
}
這裡的X軸採用的是CLAMP,所以右邊是拉伸邊緣的畫素點,Y軸採用的是MIRROR,上下都是映象的。
現在我們把drawRect註釋點,來繪製一個圓
// canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
可以看到以上的效果,所以可不可以這樣理解呢,這裡的BitmapShader就是給繪製的內容加上我們所設定的Bitmap作為背景。
RadialGradient
主要用於在某一區域內實現環形的漸變效果,RadialGradient的意思是放射漸變,即它會向一個放射源一樣,從一個點開始向外從一個顏色漸變成另一種顏色。
構造方法
RadialGradient(float centerX, float centerY, float radius,int centerColor, int edgeColor, TileMode tileMode)
- centerX:漸變中心點X座標
- centerY:漸變中心點Y座標
- radius:漸變半徑
- centerColor:漸變中心的顏色,取值型別必須是八位的0xAARRGGBB色值
- edgeColor:漸變結束的顏色
- tileMode:填充的模式RadialGradient(float centerX, float centerY, float radius,int colors[], float stops[], @NonNull TileMode tileMode)
- int[] colors:表示所需要的漸變顏色陣列
- float[] stops:表示每個漸變顏色所在的位置百分點,取值0-1
示例
下面是兩種建構函式實現的
程式碼
public class RadialGradientView extends View {
private RadialGradient mRadialGradient;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
int mWidth;
int mHeight;
public RadialGradientView(Context context) {
super(context);
}
public RadialGradientView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
// mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, mWidth / 2, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00};
float[] stops = new float[]{0f, 0.3f, 0.7f, 1f};
mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, mWidth / 2, colors, stops, Shader.TileMode.REPEAT);
mPaint.setShader(mRadialGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
}
}
再次修改一下
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00};
float[] stops = new float[]{0f, 0.3f, 0.7f, 1f};
mRadialGradient = new RadialGradient(mWidth / 2, mHeight / 2, 200, colors, stops, Shader.TileMode.REPEAT);
mPaint.setShader(mRadialGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(new Rect(0,0,mWidth,mHeight),mPaint);
}
效果如下
水波紋效果實現
程式碼如下,就不多說了
public class RippleView extends TextView {
private int mX, mY;
private ObjectAnimator mAnimator;
private int DEFAULT_RADIUS = 50;
private int mCurRadius = 0;
private RadialGradient mRadialGradient;
private Paint mPaint = new Paint();
public RippleView(Context context) {
super(context);
}
public RippleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mX != event.getX() || mY != event.getY()) {
mX = (int) event.getX();
mY = (int) event.getY();
setRadius(DEFAULT_RADIUS);
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//如果不返回true,後續的事件收不到
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (mAnimator == null) {
//這裡第一個物件傳遞當前物件,在當前物件中設定了setRadius方法,所以這裡傳遞radius
//每當值變化時就會呼叫這個setRadius方法
mAnimator = ObjectAnimator.ofInt(this, "radius", DEFAULT_RADIUS, getWidth());
}
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
setRadius(0);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
return super.onTouchEvent(event);
}
//注意這裡的方法名必須是setRadius
public void setRadius(final int radius) {
mCurRadius = radius;
if (mCurRadius > 0) {
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mX, mY, mCurRadius, mPaint);
}
}
LinearGradient
線性渲染,對某一區域實現線性漸變效果。
建構函式
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,TileMode tile)
- x0,y0是漸變的起點座標
- x1,y1是漸變的終點座標
- color0是開始顏色
- color1是結束顏色
LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],TileMode tile)
- colors和positions意義和之前的RadialGradient一樣。
示例
public class LinearGradientView extends View {
Paint mPaint = new Paint();
LinearGradient mLinearGradient;
int[] colors = new int[]{
0xFFFF0000,
0xffFF7F00,
0xffFFFF00,
0xff00FF00,
0xff00FFFF,
0xff0000FF,
0xff8B00FF};
public LinearGradientView(Context context) {
super(context);
}
public LinearGradientView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mLinearGradient == null) {
mLinearGradient = new LinearGradient(0, 0, 0, 400, colors, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
}
canvas.drawRect(new Rect(0,0,getWidth(),getHeight()),mPaint);
}
}
霓虹文字效果
這裡實現主要是繼承TextView,獲得它繪製文字的Paint,給這個Paint設定LinearGradient的Shader,把這個Shader從左邊開始向右移動,實現霓虹效果。
public class LinearGradientText extends TextView {
Paint mPaint;
LinearGradient mLinearGradient;
private Matrix mMatrix;
private int mX;
public LinearGradientText(Context context) {
super(context);
init();
}
public LinearGradientText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//注意這裡必須是TextView的Paint,因為繪製文字就是用這個Paint
mPaint = getPaint();
mMatrix = new Matrix();
}
private void initAnimtor(int width) {
ValueAnimator animator = ValueAnimator.ofInt(0, width * 2); //我們設定value的值為0-getMeasureWidth的3 倍
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mX = (Integer) animation.getAnimatedValue();
postInvalidate();
}
});
animator.setRepeatMode(ValueAnimator.RESTART); //重新播放
animator.setRepeatCount(ValueAnimator.INFINITE); //無限迴圈
animator.setDuration(2000);
animator.start();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//設定LinearGradient,繪製的範圍這裡設定的是-w到w,相當於兩個寬度,然後把Shader向右移動實現了效果
mLinearGradient = new LinearGradient(-w, 0, w, 0, new int[]{getCurrentTextColor(), Color.RED, Color.YELLOW, Color.BLUE, getCurrentTextColor(),}
, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
initAnimtor(w);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mMatrix.reset();
mMatrix.preTranslate(mX, 0);
mLinearGradient.setLocalMatrix(mMatrix);
}
}
SweepGradient
梯度渲染,是指在某一中心以x軸正方向逆時針旋轉一週而形成的掃描效果的渲染形式
建構函式
SweepGradient(float cx, float cy, int colors[], float positions[])
- cx,cy:中心座標點
- colors、positions同樣和之前一樣
SweepGradient(float cx, float cy, int color0, int color1)
- cx,cy中心座標點
- color0、color1開始和結束的顏色
簡單示例
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
mSweepGradient = new SweepGradient(w / 2, h / 2, colors, null);
mPaint.setShader(mSweepGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(new Rect(0, 0, width, height), mPaint);
}
雷達掃描實現
/**
* Created by lzy on 2017/4/13.
*/
public class RadarView extends View {
private static final int MSG_WHAT = 10086;
private static final int DELAY_TIME = 20;
//設定預設寬高,雷達一般都是圓形,所以我們下面取寬高會去Math.min(寬,高)
private final int DEFAULT_WIDTH = 200;
private final int DEFAULT_HEIGHT = 200;
private int mRadarRadius; //雷達的半徑
private Paint mRadarPaint;//雷達畫筆
private Paint mRadarBg;//雷達底色畫筆
private int radarCircleCount = 4;//雷達圓圈的個數,預設4個
private int mRadarLineColor = Color.WHITE; //雷達線條的顏色,預設為白色
private int mRadarBgColor = Color.BLACK; //雷達圓圈背景色
private Shader radarShader; //paintShader
//雷達掃描時候的起始和終止顏色
private int startColor = 0x0000ff00;
private int endColor = 0xaa00ff00;
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //設定抗鋸齒
mRadarPaint.setColor(mRadarLineColor); //畫筆顏色
mRadarPaint.setStyle(Paint.Style.STROKE); //設定空心的畫筆,只畫圓邊
mRadarPaint.setStrokeWidth(2); //畫筆寬度
mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG); //設定抗鋸齒
mRadarBg.setColor(mRadarBgColor); //畫筆顏色
mRadarBg.setStyle(Paint.Style.FILL); //設定空心的畫筆,只畫圓邊
radarShader = new SweepGradient(0, 0, startColor, endColor);
matrix = new Matrix();
}
//初始化,拓展可設定引數供佈局使用
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RadarView);
startColor = ta.getColor(R.styleable.RadarView_startColor, startColor);
endColor = ta.getColor(R.styleable.RadarView_endColor, endColor);
mRadarBgColor = ta.getColor(R.styleable.RadarView_bgColor, mRadarBgColor);
mRadarLineColor = ta.getColor(R.styleable.RadarView_lineColor, mRadarLineColor);
radarCircleCount = ta.getInteger(R.styleable.RadarView_circleCount, radarCircleCount);
ta.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureSize(1, DEFAULT_WIDTH, widthMeasureSpec);
int height = measureSize(0, DEFAULT_HEIGHT, heightMeasureSpec);
int measureSize = Math.max(width, height); //取最大的 寬|高
setMeasuredDimension(measureSize, measureSize);
}
/**
* 測繪measure
*
* @param specType 1為寬, 其他為高
* @param contentSize 預設值
*/
private int measureSize(int specType, int contentSize, int measureSpec) {
int result;
//獲取測量的模式和Size
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = Math.max(contentSize, specSize);
} else {
result = contentSize;
if (specType == 1) {
// 根據傳人方式計算寬
result += (getPaddingLeft() + getPaddingRight());
} else {
// 根據傳人方式計算高
result += (getPaddingTop() + getPaddingBottom());
}
}
return result;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRadarRadius = Math.min(w / 2, h / 2);
}
//旋轉的角度
private int rotateAngel = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mRadarRadius, mRadarRadius); //將畫板移動到螢幕的中心點
mRadarBg.setShader(null);
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg); //繪製底色(預設為黑色),可以使雷達的線看起來更清晰
for (int i = 1; i <= radarCircleCount; i++) { //根據使用者設定的圓個數進行繪製
canvas.drawCircle(0, 0, (float) (i * 1.0 / radarCircleCount * mRadarRadius), mRadarPaint); //畫圓圈
}
canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint); //繪製雷達基線 x軸
canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint); //繪製雷達基線 y軸
// canvas.rotate(rotateAngel,0,0);
//設定顏色漸變從透明到不透明
mRadarBg.setShader(radarShader);
canvas.concat(matrix);
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
}
private Matrix matrix;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
rotateAngel += 3;
postInvalidate();
matrix.reset();
matrix.preRotate(rotateAngel, 0, 0);
mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
}
};
public void startScan() {
mHandler.removeMessages(MSG_WHAT);
mHandler.sendEmptyMessage(MSG_WHAT);
}
public void stopScan() {
mHandler.removeMessages(MSG_WHAT);
}
}
ComposeShader
組合渲染
建構函式
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
- shaderA :渲染器A
- shaderB :渲染器B
- Xfermode :兩種渲染器組合的模式,Xfermode物件
ComposeShader(Shader shaderA, Shader shaderB, Mode mode)
- Mode :兩種渲染器組合的模式,ProterDuff.Mode物件
簡單例項
這裡是結合了BitmapShader和LinearGradient實現的效果public class ComposeShaderView extends View { Bitmap mBitmap; BitmapShader mBitmapShader; Paint mPaint; LinearGradient mLinearGradient; ComposeShader mComposeShader; int mWidth; int mHeight; public ComposeShaderView(Context context) { super(context); init(); } public ComposeShaderView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test_3); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mLinearGradient = new LinearGradient(0, 0, w, h, new int[] { Color.WHITE, Color.LTGRAY, Color.TRANSPARENT, Color.GREEN }, null, Shader.TileMode.CLAMP); mComposeShader = new ComposeShader(mBitmapShader, mLinearGradient, PorterDuff.Mode.MULTIPLY); mPaint.setShader(mComposeShader); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawOval(0, 0, mWidth, mHeight, mPaint); } }