版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/M075097/article/details/78533141
Android-自定义View实现二维码网格扫描+纵向雷达的扫描效果
最终效果如图:
1.可以看到整体是一个网格+纵向雷达扫描效果,因此首先我们先画出如下的静态效果,如下图
2.新建个View命名为ScanView 继承自View,并且在onLayout中初始化扫描区域为该组件中间的一块正方形
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int measuredHeight = this.getMeasuredHeight();
int measuredWidth = this.getMeasuredWidth();
int rectHeight = 5 * measuredHeight / 8;
int rectWidth = 5 * measuredWidth / 8;
int rectLen = rectHeight>rectWidth?rectWidth:rectHeight;
int rectLeft = (measuredWidth - rectLen) / 2;
int rectTop = (measuredHeight - rectLen) / 2;
mFrame = new Rect(rectLeft,rectTop,rectLeft+rectLen,rectTop+rectLen);//初始化扫描区域所在的Rect
initBoundaryAndAnimator();//初始化动画效果及边框样式
}
3.根据扫描区域的Reat绘制四个顶点的角线,此处我们使用path绘制先构建出定点角线的路径如下,初始化动画的处理可先不管
private void initBoundaryAndAnimator() {
if (mBoundaryLinePath == null) {
mCornerLineLen = mFrame.width() * mCornerLineLenRatio;//mCornerLineLenRatio 是定点处线的长度站边长的比例
mBoundaryLinePath = new Path();//初始化Path
mBoundaryLinePath.moveTo(mFrame.left, mFrame.top + mCornerLineLen);//添加要绘制线的path到路径上
mBoundaryLinePath.lineTo(mFrame.left, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.left + mCornerLineLen, mFrame.top);
mBoundaryLinePath.moveTo(mFrame.right - mCornerLineLen, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.top + mCornerLineLen);
mBoundaryLinePath.moveTo(mFrame.right, mFrame.bottom - mCornerLineLen);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.right - mCornerLineLen, mFrame.bottom);
mBoundaryLinePath.moveTo(mFrame.left + mCornerLineLen, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.left, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.left, mFrame.bottom - mCornerLineLen);
}
if (mValueAnimator == null) {
initScanValueAnim(mFrame.height());//初始化动画效果
}
}
4.然后初始化网格的路径如下,要想使绘制出来的效果是渐变的则需要在初始化画笔时添加一个Shader(着色器),此处我们需要的是一个线性渐变的效果所以我们初始化一个LinearGradient的着色器。
ps:LinearGradient 的使用比较简单,但此处我们需要注意一下我们构建时使用的模式是 LinearGradient.TileMode.CLAMP,即当着色器的不能填满所要绘制的区域时的模式为拉伸最后一个像素。而此处我们最后的0.99f-1f区域设置的是Color.TRANSPARENT,所以当填不满时会拉伸透明像素,相当于图像截取到此处,这是后面动画效果实现的关键
mScanPaint_Gridding = new Paint(Paint.ANTI_ALIAS_FLAG);
mScanPaint_Gridding.setStyle(Paint.Style.STROKE);//设置Style为STROKE
mScanPaint_Gridding.setStrokeWidth(mGriddingLineWidth);//设置线宽
//初始化网格路径,及绘制该忘咯时所需要的画笔
private void initGriddingPathAndStyle() {
if (mGriddingPath == null) {//初始化路径
mGriddingPath = new Path();
float wUnit = mFrame.width() / (mGriddingDensity + 0f);
float hUnit = mFrame.height() / (mGriddingDensity + 0f);
for (int i = 0; i <= mGriddingDensity; i++) {
mGriddingPath.moveTo(mFrame.left + i * wUnit, mFrame.top);
mGriddingPath.lineTo(mFrame.left + i * wUnit, mFrame.bottom);
}
for (int i = 0; i <= mGriddingDensity; i++) {
mGriddingPath.moveTo(mFrame.left, mFrame.top + i * hUnit);
mGriddingPath.lineTo(mFrame.right, mFrame.top + i * hUnit);
}
}
//初始化绘制该网格的画笔
if (mLinearGradient_Gridding == null) {
mLinearGradient_Gridding = new LinearGradient(0, mFrame.top, 0, mFrame.bottom + 0.01f * mFrame.height(), new int[]{Color.TRANSPARENT, Color.TRANSPARENT, mScancolor, Color.TRANSPARENT}, new float[]{0, 0.5f, 0.99f, 1f}, LinearGradient.TileMode.CLAMP);//构建着色器 注意此处的模式为:LinearGradient.TileMode.CLAMP,最后的区域0.99f到1f的区域为Color.TRANSPARENT
mLinearGradient_Gridding.setLocalMatrix(mScanMatrix);//设置本地矩阵,以便操作着色器偏移以产生动画效果
mScanPaint_Gridding.setShader(mLinearGradient_Gridding);//给画笔设置着色器
}
}
5.然后我们同样再初始化一个画笔用来叠加一个区域的渐变效果
叠加渐变效果的画笔的初始化为
mScanPaint_Radio = new Paint(Paint.ANTI_ALIAS_FLAG);//默认的Paint模式为Fill
mScanPaint_Radio.setStyle(Paint.Style.FILL);
Resources resources = getResources();
mScancolor = resources.getColor(R.color.scancolor);
//初始化画笔的样式
private void initRadarStyle() {
if (mLinearGradient_Radar == null) {
mLinearGradient_Radar = new LinearGradient(0, mFrame.top, 0, mFrame.bottom + 0.01f * mFrame.height(), new int[]{Color.TRANSPARENT, Color.TRANSPARENT, mScancolor, Color.TRANSPARENT}, new float[]{0, 0.85f, 0.99f, 1f}, LinearGradient.TileMode.CLAMP);//构建着色器
mLinearGradient_Radar.setLocalMatrix(mScanMatrix);//给着色器设置变换矩阵
mScanPaint_Radio.setShader(mLinearGradient_Radar);//给画笔设置着色器
}
}
6.在Ondraw中绘制如下
canvas.drawPath(mGriddingPath, mScanPaint_Gridding);//使用添加了着色器的画笔绘制网格
canvas.drawRect(mFrame, mScanPaint_Radio);//使用添加了着色器的画笔绘制该矩形区域
至此我们已经能够得到一个上面提到的静态效果,下面要做的就是把它动起来
7.在前面初始化网格画笔和叠加渐变效果的画笔之后,都给画笔设置了一个矩阵,该矩阵是我们初始化的一个普通的矩阵,我们可以通过操作该矩阵,然后再设置到画笔上,重新绘制整个View即可达到动画的效果
//初始化的用来给着色器使用的变换矩阵
mScanMatrix = new Matrix();
mScanMatrix.setTranslate(0, 30);
//------------------------------
mLinearGradient_Radar.setLocalMatrix(mScanMatrix);//给着色器设置变换矩阵
mLinearGradient_Gridding.setLocalMatrix(mScanMatrix);//设置本地矩阵,以便操作着色器偏移以产生动画效果
8.因此我们初始化一个值动画,使其取值从0-绘制区域的高度,开启动画后,不断用动画值操作偏移矩阵,并设置着色器,然后重绘整个view。代码如下:
public void initScanValueAnim(int height) {
mValueAnimator = new ValueAnimator();
mValueAnimator.setDuration(mScanAnimatorDuration);
mValueAnimator.setFloatValues(-height, 0);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimator.setInterpolator(new DecelerateInterpolator());
mValueAnimator.setRepeatCount(Integer.MAX_VALUE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mScanMatrix != null && mLinearGradient_Gridding != null/* && mScanPaint != null*/) {
float animatedValue = (float) animation.getAnimatedValue();
mScanMatrix.setTranslate(0, animatedValue);//操作偏移矩阵
mLinearGradient_Gridding.setLocalMatrix(mScanMatrix);//设置矩阵到网格画笔的着色器
mLinearGradient_Radar.setLocalMatrix(mScanMatrix);//设置便宜后的矩阵到渐变效果的作色器上
//mScanPaint.setShader(mLinearGradient); 不是必须的设置到shader即可
// Log.d("anim", "onAnimationUpdate: " + animatedValue);
invalidate();//重回
}
}
});
mValueAnimator.start();//启动动画
}
9.最后贴出ScanView的代码如下,以供参考
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.yongzhongjun.weiyang.mozxing.R;
/**
* Created by weiyang on 2017/10/1.
* this is the view of scan area,and this view need get the Area(Rect) from the CameraManager,this is why it need extend from AScanView
*/
public class ScanView extends View {
public static final int style_gridding = 0;//扫描区域的样式
public static final int style_radar = 1;
public static final int style_hybrid = 2;
private Rect mFrame;//最佳扫描区域的Rect
private Paint mLinePaint;//边框画笔
private Paint mScanPaint_Gridding;//网格样式画笔
private Paint mScanPaint_Radio;//雷达样式画笔
private Path mBoundaryLinePath;//边框path
private int mBoundaryColor = Color.WHITE;
private float mBoundaryStrokeWidth = 8f;//扫描区域边线样式-线宽
private Path mGriddingPath;//网格样式的path
private LinearGradient mLinearGradient_Radar;//雷达样式的画笔shader
private LinearGradient mLinearGradient_Gridding;//网格画笔的shader
private float mGriddingLineWidth = 2;//网格线的线宽,单位pix
private int mGriddingDensity = 40;//网格样式的,网格密度,值越大越密集
private float mCornerLineLenRatio = 0.06f;//扫描边框角线占边总长的比例
private float mCornerLineLen = 50f;//根据比例计算的边框长度,从四角定点向临近的定点画出的长度
private Matrix mScanMatrix;//变换矩阵,用来实现动画效果
private ValueAnimator mValueAnimator;//值动画,用来变换矩阵操作
private int mScanAnimatorDuration = 1800;//值动画的时长
private int mScancolor;//扫描颜色
private int mScanStyle = 2;//网格 0:网格,1:纵向雷达 2:综合
public ScanView(Context context) {
this(context, null);
}
// This constructor is used when the class is built from an XML resource.
public ScanView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScanView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// Initialize these once for performance rather than calling them every time in onDraw().
mScanPaint_Gridding = new Paint(Paint.ANTI_ALIAS_FLAG);
mScanPaint_Gridding.setStyle(Paint.Style.STROKE);
mScanPaint_Gridding.setStrokeWidth(mGriddingLineWidth);
mScanPaint_Radio = new Paint(Paint.ANTI_ALIAS_FLAG);
mScanPaint_Radio.setStyle(Paint.Style.FILL);
Resources resources = getResources();
mScancolor = resources.getColor(R.color.scancolor);
//扫描区域的四角线框的样式
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setColor(mBoundaryColor);
mLinePaint.setStrokeWidth(mBoundaryStrokeWidth);
mLinePaint.setStyle(Paint.Style.STROKE);
//变换矩阵,用来处理扫描的上下扫描效果
mScanMatrix = new Matrix();
mScanMatrix.setTranslate(0, 30);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int measuredHeight = this.getMeasuredHeight();
int measuredWidth = this.getMeasuredWidth();
int rectHeight = 5 * measuredHeight / 8;
int rectWidth = 5 * measuredWidth / 8;
int rectLen = rectHeight>rectWidth?rectWidth:rectHeight;
int rectLeft = (measuredWidth - rectLen) / 2;
int rectTop = (measuredHeight - rectLen) / 2;
mFrame = new Rect(rectLeft,rectTop,rectLeft+rectLen,rectTop+rectLen);
initBoundaryAndAnimator();
}
private void initBoundaryAndAnimator() {
if (mBoundaryLinePath == null) {
mCornerLineLen = mFrame.width() * mCornerLineLenRatio;
mBoundaryLinePath = new Path();
mBoundaryLinePath.moveTo(mFrame.left, mFrame.top + mCornerLineLen);
mBoundaryLinePath.lineTo(mFrame.left, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.left + mCornerLineLen, mFrame.top);
mBoundaryLinePath.moveTo(mFrame.right - mCornerLineLen, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.top);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.top + mCornerLineLen);
mBoundaryLinePath.moveTo(mFrame.right, mFrame.bottom - mCornerLineLen);
mBoundaryLinePath.lineTo(mFrame.right, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.right - mCornerLineLen, mFrame.bottom);
mBoundaryLinePath.moveTo(mFrame.left + mCornerLineLen, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.left, mFrame.bottom);
mBoundaryLinePath.lineTo(mFrame.left, mFrame.bottom - mCornerLineLen);
}
if (mValueAnimator == null) {
initScanValueAnim(mFrame.height());
}
}
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if (mFrame == null||mBoundaryLinePath==null) {
return;
}
canvas.drawPath(mBoundaryLinePath, mLinePaint);
switch (mScanStyle) {
case style_gridding:
default:
initGriddingPathAndStyle();
canvas.drawPath(mGriddingPath, mScanPaint_Gridding);
break;
case style_radar:
initRadarStyle();
canvas.drawRect(mFrame, mScanPaint_Radio);
break;
case style_hybrid:
initGriddingPathAndStyle();
initRadarStyle();
canvas.drawPath(mGriddingPath, mScanPaint_Gridding);
canvas.drawRect(mFrame, mScanPaint_Radio);
break;
}
}
private void initRadarStyle() {
if (mLinearGradient_Radar == null) {
mLinearGradient_Radar = new LinearGradient(0, mFrame.top, 0, mFrame.bottom + 0.01f * mFrame.height(), new int[]{Color.TRANSPARENT, Color.TRANSPARENT, mScancolor, Color.TRANSPARENT}, new float[]{0, 0.85f, 0.99f, 1f}, LinearGradient.TileMode.CLAMP);
mLinearGradient_Radar.setLocalMatrix(mScanMatrix);
mScanPaint_Radio.setShader(mLinearGradient_Radar);
}
}
private void initGriddingPathAndStyle() {
if (mGriddingPath == null) {
mGriddingPath = new Path();
float wUnit = mFrame.width() / (mGriddingDensity + 0f);
float hUnit = mFrame.height() / (mGriddingDensity + 0f);
for (int i = 0; i <= mGriddingDensity; i++) {
mGriddingPath.moveTo(mFrame.left + i * wUnit, mFrame.top);
mGriddingPath.lineTo(mFrame.left + i * wUnit, mFrame.bottom);
}
for (int i = 0; i <= mGriddingDensity; i++) {
mGriddingPath.moveTo(mFrame.left, mFrame.top + i * hUnit);
mGriddingPath.lineTo(mFrame.right, mFrame.top + i * hUnit);
}
}
if (mLinearGradient_Gridding == null) {
mLinearGradient_Gridding = new LinearGradient(0, mFrame.top, 0, mFrame.bottom + 0.01f * mFrame.height(), new int[]{Color.TRANSPARENT, Color.TRANSPARENT, mScancolor, Color.TRANSPARENT}, new float[]{0, 0.5f, 0.99f, 1f}, LinearGradient.TileMode.CLAMP);
mLinearGradient_Gridding.setLocalMatrix(mScanMatrix);
mScanPaint_Gridding.setShader(mLinearGradient_Gridding);
}
}
public void initScanValueAnim(int height) {
mValueAnimator = new ValueAnimator();
mValueAnimator.setDuration(mScanAnimatorDuration);
mValueAnimator.setFloatValues(-height, 0);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimator.setInterpolator(new DecelerateInterpolator());
mValueAnimator.setRepeatCount(Integer.MAX_VALUE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mScanMatrix != null && mLinearGradient_Gridding != null/* && mScanPaint != null*/) {
float animatedValue = (float) animation.getAnimatedValue();
mScanMatrix.setTranslate(0, animatedValue);
mLinearGradient_Gridding.setLocalMatrix(mScanMatrix);
mLinearGradient_Radar.setLocalMatrix(mScanMatrix);
//mScanPaint.setShader(mLinearGradient); 不是必须的设置到shader即可
// Log.d("anim", "onAnimationUpdate: " + animatedValue);
invalidate();
}
}
});
mValueAnimator.start();
}
@Override
protected void onDetachedFromWindow() {
if (mValueAnimator != null && mValueAnimator.isRunning()) {
mValueAnimator.cancel();
}
super.onDetachedFromWindow();
}
//设定扫描的颜色
public void setScancolor(int colorValue) {
this.mScancolor = colorValue;
}
public void setScanAnimatorDuration(int duration) {
this.mScanAnimatorDuration = duration;
}
/*
* @params colorValue:扫描区域边框的颜色
* @params strokeWidth:扫描区域外边框的线宽
* @params cornerLineLen:扫描区域外边框四角的线长比例
* */
public void setBoundaryStyle(int colorValue, int strokeWidth, float cornerLineLenRatio) {
this.mBoundaryColor = colorValue;
this.mBoundaryStrokeWidth = strokeWidth;
this.mCornerLineLenRatio = cornerLineLenRatio;
}
/*
* @description 扫描区域的样式
* @scanStyle
*
* */
public void setScanStyle(int scanStyle) {
this.mScanStyle = scanStyle;
}
/*
* 扫描区域网格的样式
* @params strokeWidth:网格的线宽
* @params density:网格的密度
* */
public void setScanGriddingStyle(float strokeWidh, int density) {
this.mGriddingLineWidth = strokeWidh;
this.mGriddingDensity = density;
}
}