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);            }        }    }}
1 0
原创粉丝点击