Android自定义View(五)——带扫描线的View
来源:互联网 发布:淘宝ltvvy模特是谁 编辑:程序博客网 时间:2024/05/01 02:11
因为技术原理比较简单,所以就不详细赘述实现的细节了。
效果需求
一个具有圆形背景的等宽等高的视图上,上下来回滚动一个渐变的矩形,矩形的两边不能超出圆,也不能比圆小。
遇到的问题
如果只看上面这句话,大家都会觉得很简单,在视图上绘制一个圆,再绘制一个渐变的矩形,控制坐标来移动矩形就好了。但当大家实际操作的时候可能就会遇到这样一个问题:矩形绘制的时候总感觉很不协调,达不到预期效果。为什么呢?因为android默认的视图轮廓都是矩形的,即使背景是个圆,但是轮廓还是矩形,拟绘制的矩形宽度不变的话,就像一根固定的棍子在圆内滚动,还会超出圆的边界;如果动态设置矩形的宽度,矩形的两端还是不能平滑的和圆重叠。
解决的一些思路
1.如果是Android5.0及以上的系统,这个问题很好解决,有一个方法:View.setClipToOutline(boolean clip)或者在xml里android:clipToOutline =boolean,设置成true后,给view设置一个任何形状的背景,画矩形时把矩形的宽度设得比view个宽度大些或者相等,那么绘制的矩形两端就会很平滑的和圆相切,像是被圆的边盖住了一样;
2.如果是Android5.0以下的系统,也不要灰心,我们可以从裁剪Canvas入手。说到底,View的绘制还是在Canvas上面进行的,只要我们能把Canvas裁剪成一个以view的中心为圆心,以view的宽度的一半或高度的一半为半径的圆,那么不管滚动的矩形多宽,也只能绘制在这个圆形的画板上,多月部分被平滑的截掉了,达到了预期效果。
作为一个自定义view新手,可能这些方法对大牛们而言就是小儿科,但是我却花了一天多时间才折腾出来,大家随意看看就好。
效果图
代码
/** * com.ykb.json.customview * 描述 :带扫描线的ImageView * 作者 : ykb * 时间 : 15/11/4. */public class ScanningImageView extends ImageView { private static final int CHANGE_BOUNDS = 50; private Paint mPaint; private int mHeight = 0; private Path mPath; public ScanningImageView(Context context) { this(context, null); } public ScanningImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScanningImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setClipToOutline(true);//设置绘制的覆盖物不能超出背景的轮廓 } mPath = new Path(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.TRANSPARENT); mPaint.setAlpha(255); } @Override protected void onDraw(Canvas canvas) { mHeight += 10; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mPath.reset(); canvas.clipPath(mPath); mPath.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, Path.Direction.CCW); canvas.clipPath(mPath, Region.Op.REPLACE); } LinearGradient linearGradient = new LinearGradient(0, mHeight - CHANGE_BOUNDS, 0, mHeight, new int[]{Color.TRANSPARENT, Color.WHITE}, null, Shader.TileMode.CLAMP); mPaint.setShader(linearGradient); canvas.drawRect(0, mHeight - CHANGE_BOUNDS, getWidth(), mHeight, mPaint); if (mHeight >= getHeight()) { mHeight = 0; } postInvalidateDelayed(40); super.onDraw(canvas); }}
======================我是华丽的分割线==========================
因为当时没有适配机型测试,后来发现在三星等手机5.0以下的系统版本上会出现无法裁剪的bug,现在来修正一下以前的做法o(╯□╰)o
1.首先,在5.0以下的手机上,必须把这个视图关闭硬件加速——解决不能正常裁剪的问题;
2.把构造方法里的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setClipToOutline(true);//设置绘制的覆盖物不能超出背景的轮廓 }
去掉;
3.把onDraw方法里的
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mPath.reset(); canvas.clipPath(mPath); mPath.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, Path.Direction.CCW); canvas.clipPath(mPath, Region.Op.REPLACE); }
if判断去掉
@Override protected void onDraw(Canvas canvas) { mHeight += 10; mPath.reset(); canvas.clipPath(mPath); mPath.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, Path.Direction.CCW); canvas.clipPath(mPath, Region.Op.REPLACE); LinearGradient linearGradient = new LinearGradient(0, mHeight - CHANGE_BOUNDS, 0, mHeight, new int[]{Color.TRANSPARENT, Color.WHITE}, null, Shader.TileMode.CLAMP); mPaint.setShader(linearGradient); canvas.drawRect(0, mHeight - CHANGE_BOUNDS, getWidth(), mHeight, mPaint); if (mHeight >= getHeight()) { mHeight = 0; } postInvalidateDelayed(40); super.onDraw(canvas); }
执行以上三个操作后,基本可以适配绝大部分机型了。
但如果你认为这样就完了,那还真的没完。。。
后来没事的时候,我有捣鼓了一下以前的一些项目,其中就包括这个扫描ImageView,现在我把它弄得更“臃肿”了——集合了可以裁剪为圆形ImageView的功能:
/** * 包名:com.ykb.json.customview * 描述:可裁剪为圆形和自带扫描线的<var>ImageView</var> * 创建者:yankebin * 日期:2015/12/15 */public class RoundScanningImageView extends ImageView { private Paint mPaint; private int mHeight = 0; private Path mPath; private float centerX; private float centerY; private float moveSpeed; private float outStrokeWidth; private int outStrokeColor; private int outStrokeAlpha; private boolean enableClipPathRound; private boolean enableScan; private float strokeWidth; private float scanLineHeight; private int invalidateTime; private PorterDuffXfermode porterDuffXfermode; public RoundScanningImageView(Context context) { this(context, null); } public RoundScanningImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundScanningImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //此处容易抛异常,导致关闭硬件加速失败 try { setLayerType(LAYER_TYPE_SOFTWARE, null); } catch (Exception e) { e.printStackTrace(); } TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.RoundScanningImageView); moveSpeed = typedArray.getFloat(R.styleable.RoundScanningImageView_scan_speed, 10); outStrokeWidth = typedArray.getFloat(R.styleable.RoundScanningImageView_out_stroke_width, 10); outStrokeColor = typedArray.getColor(R.styleable.RoundScanningImageView_out_stroke_color, Color.LTGRAY); outStrokeAlpha = typedArray.getInt(R.styleable.RoundScanningImageView_out_stroke_alpha, 100); enableClipPathRound = typedArray.getBoolean(R.styleable.RoundScanningImageView_enable_clipPath_round, true); enableScan = typedArray.getBoolean(R.styleable.RoundScanningImageView_enable_scan, true); strokeWidth = typedArray.getFloat(R.styleable.RoundScanningImageView_stroke_width, 10); scanLineHeight = typedArray.getFloat(R.styleable.RoundScanningImageView_scan_line_height, 50); invalidateTime = typedArray.getInt(R.styleable.RoundScanningImageView_scan_invalidate_time, 50); typedArray.recycle(); porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); mPath = new Path(); mPaint = new Paint(); } /** * 创建渲染器 * * @return */ private LinearGradient buildLinearGradient() { LinearGradient linearGradient = new LinearGradient(0, mHeight - scanLineHeight, 0, mHeight, new int[]{Color.TRANSPARENT, Color.WHITE}, null, Shader.TileMode.CLAMP); return linearGradient; } /** * 画笔重置 * * @param color * @param alpha */ private void resetPaint(int color, int alpha) { mPaint.reset(); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setColor(color); mPaint.setAlpha(alpha); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); centerX = getWidth() / 2; centerY = getHeight() / 2;// ViewParent parent = getParent();// if (null != parent && parent instanceof ViewGroup) {// ((ViewGroup) parent).setLayerType(LAYER_TYPE_SOFTWARE, null);// } } /** * 获取裁剪后的圆形图片 * * @param bmp * @param radius * @return */ private Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth, squareHeight; int x, y; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大于宽 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形图片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 宽大于高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); return output; } @Override protected void onDraw(Canvas canvas) { if (!enableClipPathRound && !enableScan) { super.onDraw(canvas); } else { if (enableClipPathRound) { int radius = getWidth() > getHeight() ? getHeight() / 2 : getWidth() / 2; radius -= outStrokeWidth / 2; //绘制圆边 resetPaint(outStrokeColor, outStrokeAlpha); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(outStrokeWidth); canvas.drawCircle(centerX, centerY, radius, mPaint); //处理图片 Drawable drawable = getDrawable(); if (null != drawable) { Bitmap mBitmap = ((BitmapDrawable) drawable).getBitmap(); if (null != mBitmap) { radius -= strokeWidth; //裁剪图片为圆形 Bitmap roundBitmap = getCroppedRoundBitmap(mBitmap, radius); if (null != roundBitmap) { try { canvas.drawBitmap(roundBitmap, centerX - radius, centerY - radius, null); roundBitmap.recycle(); } catch (Exception e) { e.printStackTrace(); } } } } } else { super.onDraw(canvas); } if (enableScan) { //移动扫描白线的位置 mHeight += moveSpeed; if (enableClipPathRound) { //裁剪画布 mPath.reset(); canvas.clipPath(mPath); // makes the clip empty mPath.addCircle(centerX, centerY, centerX - strokeWidth - outStrokeWidth / 2, Path.Direction.CCW); canvas.clipPath(mPath, Region.Op.REPLACE); } //绘制扫描线 resetPaint(Color.TRANSPARENT, 255); mPaint.setXfermode(porterDuffXfermode); mPaint.setShader(buildLinearGradient()); canvas.drawRect(0, mHeight - scanLineHeight, getWidth(), mHeight, mPaint); if (mHeight >= getHeight()) { mHeight = 0; } postInvalidateDelayed(invalidateTime); } } }}
自定义的属性:
<declare-styleable name="RoundScanningImageView"> <attr name="out_stroke_width" format="float"/> <attr name="enable_scan" format="boolean"/> <attr name="scan_speed" format="float"/> <attr name="enable_clipPath_round" format="boolean"/> <attr name="stroke_width" format="float"/> <attr name="out_stroke_color" format="color"/> <attr name="out_stroke_alpha" format="integer"/> <attr name="scan_line_height" format="float"/> <attr name="scan_invalidate_time" format="integer"/> </declare-styleable>
=============================再一次优化=============================
优化如下:
引入弱引用缓存相关图片,避免高频率gc
public class RoundScanningImageView extends ImageView { private Paint mPaint; private int mHeight = 0; private Path mPath; private float centerX; private float centerY; private float moveSpeed; private float outStrokeWidth; private int outStrokeColor; private int outStrokeAlpha; private boolean enableClipPathRound; private boolean enableScan; private float strokeWidth; private float scanLineHeight; private int invalidateTime; private PorterDuffXfermode porterDuffXfermode; private Drawable mLastDrawable; private WeakReference<Bitmap> mTempBitmap; private WeakReference<Bitmap> mLastBitmap; public RoundScanningImageView(Context context) { this(context, null); } public RoundScanningImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundScanningImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); try { setLayerType(LAYER_TYPE_SOFTWARE, null); } catch (Exception e) { e.printStackTrace(); } TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.RoundScanningImageView); moveSpeed = typedArray.getFloat(R.styleable.RoundScanningImageView_scan_speed, 10); outStrokeWidth = typedArray.getFloat(R.styleable.RoundScanningImageView_out_stroke_width, 10); outStrokeColor = typedArray.getColor(R.styleable.RoundScanningImageView_out_stroke_color, Color.LTGRAY); outStrokeAlpha = typedArray.getInt(R.styleable.RoundScanningImageView_out_stroke_alpha, 100); enableClipPathRound = typedArray.getBoolean(R.styleable.RoundScanningImageView_enable_clipPath_round, true); enableScan = typedArray.getBoolean(R.styleable.RoundScanningImageView_enable_scan, true); strokeWidth = typedArray.getFloat(R.styleable.RoundScanningImageView_stroke_width, 10); scanLineHeight = typedArray.getFloat(R.styleable.RoundScanningImageView_scan_line_height, 50); invalidateTime = typedArray.getInt(R.styleable.RoundScanningImageView_scan_invalidate_time, 50); typedArray.recycle(); porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); mPath = new Path(); mPaint = new Paint(); } /** * 创建渲染器 * * @return */ private LinearGradient buildLinearGradient() { LinearGradient linearGradient = new LinearGradient(0, mHeight - scanLineHeight, 0, mHeight, new int[]{Color.TRANSPARENT, Color.WHITE}, null, Shader.TileMode.CLAMP); return linearGradient; } /** * 画笔重置 * * @param color * @param alpha */ private void resetPaint(int color, int alpha) { mPaint.reset(); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setColor(color); mPaint.setAlpha(alpha); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); centerX = getWidth() / 2; centerY = getHeight() / 2; } /** * 获取裁剪后的圆形图片 * * @param bmp * @param radius * @return */ private Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth, squareHeight; int x, y; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大于宽 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形图片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 宽大于高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); return output; } @Override protected void onDraw(Canvas canvas) { if (!enableClipPathRound && !enableScan) { super.onDraw(canvas); } else { if (enableClipPathRound) { int radius = getWidth() > getHeight() ? getHeight() / 2 : getWidth() / 2; radius -= outStrokeWidth / 2; //绘制圆边 resetPaint(outStrokeColor, outStrokeAlpha); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(outStrokeWidth); canvas.drawCircle(centerX, centerY, radius, mPaint); //处理图片 Drawable drawable = getDrawable(); if (null != drawable) { boolean isSameImg = true; if (null != mLastDrawable) { if (drawable.getConstantState() != mLastDrawable.getConstantState()) { isSameImg = false; mLastDrawable = drawable; } } else { isSameImg = false; mLastDrawable = drawable; } boolean needCreate = false; Bitmap roundBitmap = null; if (isSameImg) { roundBitmap = null == mLastBitmap || null == mLastBitmap.get() ? null : mLastBitmap.get(); if (null == roundBitmap) { needCreate = true; } } else { needCreate = true; } if (needCreate) { Bitmap mBitmap = null; //防止引用上一次的缓存原图 boolean needCreateTemp=false; if(isSameImg){ mBitmap = null == mTempBitmap || null == mTempBitmap.get() ? null : mTempBitmap.get(); if (null == mBitmap) { needCreateTemp=true; } }else { needCreateTemp=true; } if(needCreateTemp){ mBitmap = ((BitmapDrawable) mLastDrawable).getBitmap(); mTempBitmap = new WeakReference<>(mBitmap); } if (null != mBitmap) { radius -= strokeWidth; //裁剪图片为圆形 roundBitmap = getCroppedRoundBitmap(mBitmap, radius); if (null != roundBitmap) { mLastBitmap = new WeakReference<>(roundBitmap); } } } else { radius -= strokeWidth; } if (null != roundBitmap) { canvas.drawBitmap(roundBitmap, centerX - radius, centerY - radius, null); } } //裁剪画布 mPath.reset(); canvas.clipPath(mPath); // makes the clip empty mPath.addCircle(centerX, centerY, centerX - strokeWidth - outStrokeWidth / 2, Path.Direction.CCW); canvas.clipPath(mPath, Region.Op.REPLACE); } else { super.onDraw(canvas); } if (enableScan) { //移动扫描白线的位置 mHeight += moveSpeed; //绘制扫描线 resetPaint(Color.TRANSPARENT, 255); mPaint.setXfermode(porterDuffXfermode); mPaint.setShader(buildLinearGradient()); canvas.drawRect(0, mHeight - scanLineHeight, getWidth(), mHeight, mPaint); if (mHeight >= getHeight()) { mHeight = 0; } postInvalidateDelayed(invalidateTime); } } }}
- Android自定义View(五)——带扫描线的View
- Android自定义View——带数字的进度条
- Android 自定义View( 雷达扫描)
- Android 自定义View (五)——实践
- Android 自定义View (五)
- Android 自定义View (五)
- Android自定义View—仿雷达扫描效果
- Android—自定义view
- Android自定义View研究(五)--View的大小
- Android自定义View研究(五)--View的大小
- Android自定义View研究(五)--View的大小
- Android自定义View研究(五)--View的大小
- android自定义View(带旋转动画的饼状图)
- Android-五子连珠(三)-自定义的view
- Android 自定义View——带下载进度Button
- Android 自定义View——带进度条按钮
- Android 自定义View——View 基础知识
- Android学习自定义View(五)——自定义ViewGroup及其onMeasure()的理解
- 非虫——JNI实例
- Python学习之路七---进阶知识
- SpringJDBC--NamedParameterJdbcTemplate
- 关于spark提交作业报错原因
- Activity之间的数据传输
- Android自定义View(五)——带扫描线的View
- PHP的优劣势,欢迎指正和补充
- android service完全解析(下)
- 欢迎使用CSDN-markdown编辑器
- OC 基础第七讲 : NSDate,类的扩展与延展(Extension)
- ArcGIS 实现框选区域查询要素后,要素点击弹出框
- 使用csc生成DLL文件
- 支付宝接口的链接地址
- 关于行为识别的综述Human Activity Analysis : A Review