八个功用庞大的圆角图片库,原本新手指引ShowCase这么简单就能够兑现了

近来有一个须要,供给在app首页增多新手教导效应。如此敏感的本身须臾间就悟出了showcase那首要字,于是从github上一搜就找到四个700+star的FancyShowCaseView。那么大家今日就以它为根基,去落到实处和谐的ShowCaseView小编要好写的精简版也一路放出ShowCaseView

在上一篇文章【ImageView】自定义ImageView体系(三)——一个效能庞大的圆角图影片仓库(上)
中,介绍了原版的书文者对于开源项目
SelectableRoundedImageView
的需要出现背景和才能实现思路。那篇小说,通过对宗旨自定义View源码的剖释,学习一下它的贯彻原理。

图片 1基础知识

职能呈现

图片 2效果1图片 3效果2

SelectableRoundedImageView.png

Android里,大家经常会用shape去定义View的造型。如下是在xml里定义一个简短shape的代码:

规律表达

简短的看一下,showcase是漂浮在最上层同一时候还隐含镂空高亮的区域的三个View。大家来拆除与搬迁一下关键点:

  1. 浮动在最高层:DecorView为任何Window分界面包车型客车最顶层View,大家的自定义视图无疑要增添到DevorView上
  2. 镌刻高亮:正是画三个新图层,然后两图层交集部分改为全透明,那几个的确要用PorterDuffXfermode的CLEA凯雷德达成

一经你说自身曾经想到了,那么OK,后面包车型地铁篇章你就足以不用看了,因为那东西正是这么简单

Java源码:

<?xml version="1.0" encoding="utf-8"?><shape xmlns:andro > <solid android:color="#00FF00" /> <corners android:bottomLeftRadius="10dp" android:bottomRightRadius="10dp" android:topLeftRadius="10dp" android:topRightRadius="10dp" /></shape>

福寿康宁镂空的ImageView

其实不自然非若是ImageView,任性View在onDraw方法里面都能落得相应的效用该ImageView只拥有镂空效果,所以那边只关乎到普通绘制部分。首先注明四个变量,那多少个变量代表大家ImageView上的任意成分所利用到的Paint

// 设置背景PaintPaint mBackgroundPaint;// 设置高亮点清除中心PaintPaint mErasePaint;// 设置高亮点PaintPaint mCircleBorderPaint;

跟着在构造方法中实行初始化。mBackgroundPaint便是大家的背景绘制Paint,m伊RussePaint是雕刻绘制Paint,mCircleBorderPaint就是大家上海体育场所在雕琢分局增多虚线绘制Paint

mBackgroundPaint=new Paint();mBackgroundPaint.setAntiAlias;mErasePaint=new Paint();mErasePaint.setAntiAlias;mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));mErasePaint.setAlpha;mCircleBorderPaint=new Paint();mCircleBorderPaint.setAntiAlias;mCircleBorderPaint.setColor(mFocusBorderColor);mCircleBorderPaint.setStrokeWidth(mFocusBorderSize);mCircleBorderPaint.setStyle(Paint.Style.STROKE);mCircleBorderPaint.setStrokeJoin(Paint.Join.ROUND);mCircleBorderPaint.setStrokeCap(Paint.Cap.ROUND);mCircleBorderPaint.setPathEffect(new DashPathEffect(new float[] {10, 20}, 0));

布局完毕之后,就是发端画了。在画从前大家先明了一下,镂空图形能够是自由的图纸,比如圆形、圆角矩形等,所以那边先用多少个枚举来给客户提供绘制采纳。这里为了演示,仅提供圆形与圆角矩形2种

public enum FocusShape { CIRCLE, ROUNDED_RECTANGLE}

有了图片之后,我们将要思虑使用者怎样将他所企望的图形以目的的款式传递到onDraw()方法里面。这里我们就需求传递一个bean进来。那个bean就总结绘制时所需的各样参数

public class CalculatorBean { FocusShape mFocusShape; int mCircleCenterX; int mCircleCenterY; int mCircleRadius; // 圆角矩形专用 int mFocusWidth; int mFocusHeight;}

透过set方法传进全部镂空部分的View

public void setmCalculatorBeen(ArrayList<CalculatorBean> mCalculatorBeen) { this.mCalculatorBeen = mCalculatorBeen;}

剩余就从头绘制了。先绘制整体的背景象,再产生镂空,并累加一定的点缀

@Overrideprotected void onDraw(Canvas canvas) { super.onDraw; if (mBitmap == null) { mBitmap= Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); // 把图片设置成半透明 mBitmap.eraseColor(0xb2000000); } canvas.drawBitmap(mBitmap, 0, 0, mBackgroundPaint); if (mCalculatorBeen!=null) { for (CalculatorBean calculatorBean : mCalculatorBeen) { if (calculatorBean.getmFocusShape()==FocusShape.CIRCLE) { drawCircle(canvas, calculatorBean); } else if (calculatorBean.getmFocusShape()==FocusShape.ROUNDED_RECTANGLE) { drawRoundedRectangle(canvas, calculatorBean); } } }}

画圆的办法

private void drawCircle(Canvas canvas, CalculatorBean calculatorBean) { // 绘制高亮 canvas.drawCircle(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY(), calculatorBean.getmCircleRadius()+ mAnimCounter*animMoveFactor, mErasePaint); // 绘制其余部分 mPath.reset(); mPath.moveTo(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY; mPath.addCircle(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY(), calculatorBean.getmCircleRadius()+ mAnimCounter*animMoveFactor, Path.Direction.CW); canvas.drawPath(mPath, mCircleBorderPaint);}

画圆角矩形的章程

private void drawRoundedRectangle(Canvas canvas, CalculatorBean calculatorBean) { // 绘制高亮 int centerX=calculatorBean.getmCircleCenterX(); int centerY=calculatorBean.getmCircleCenterY(); float left=centerX-calculatorBean.getmFocusWidth()/2- mAnimCounter*animMoveFactor; float top=centerY-calculatorBean.getmFocusHeight()/2- mAnimCounter*animMoveFactor; float right=centerX+calculatorBean.getmFocusWidth()/2+ mAnimCounter*animMoveFactor; float bottom=centerY+calculatorBean.getmFocusHeight()/2+ mAnimCounter*animMoveFactor; canvas.drawRoundRect(new RectF(left, top, right, bottom), calculatorBean.getmCircleRadius(), calculatorBean.getmCircleRadius(), mErasePaint); // 绘制其余部分 mPath.reset(); mPath.moveTo(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY; mPath.addRoundRect(new RectF(left, top, right, bottom), calculatorBean.getmCircleRadius(), calculatorBean.getmCircleRadius(), Path.Direction.CW); canvas.drawPath(mPath, mCircleBorderPaint);}

增添动画作用,这里是通过改变圆形半径可能圆角矩形的长度宽度来达到动画效果

if (mAnimationEnabled) { if (mAnimCounter==ANIM_COUNTER_MAX) { mStep=-1; } else if (mAnimCounter==0) { mStep=1; } mAnimCounter+=mStep; postInvalidate();}
package com.joooonho;

import com.joooonho.R;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

public class SelectableRoundedImageView extends ImageView {

    public static final String TAG = "SelectableRoundedImageView";

    private int mResource = 0;

    private static final ScaleType[] sScaleTypeArray = {
        ScaleType.MATRIX,
        ScaleType.FIT_XY,
        ScaleType.FIT_START,
        ScaleType.FIT_CENTER,
        ScaleType.FIT_END,
        ScaleType.CENTER,
        ScaleType.CENTER_CROP,
        ScaleType.CENTER_INSIDE
    };

    // Set default scale type to FIT_CENTER, which is default scale type of
    // original ImageView.
    private ScaleType mScaleType = ScaleType.FIT_CENTER;

    private float mLeftTopCornerRadius = 0.0f;
    private float mRightTopCornerRadius = 0.0f;
    private float mLeftBottomCornerRadius = 0.0f;
    private float mRightBottomCornerRadius = 0.0f;

    private float mBorderWidth = 0.0f;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);

    private boolean isOval = false;

    private Drawable mDrawable;

    private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 };

    public SelectableRoundedImageView(Context context) {
        super(context);
    }

    public SelectableRoundedImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SelectableRoundedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.SelectableRoundedImageView, defStyle, 0);

        final int index = a.getInt(R.styleable.SelectableRoundedImageView_android_scaleType, -1);
        if (index >= 0) {
            setScaleType(sScaleTypeArray[index]);
        }

        mLeftTopCornerRadius = a.getDimensionPixelSize(
                R.styleable.SelectableRoundedImageView_sriv_left_top_corner_radius, 0);
        mRightTopCornerRadius = a.getDimensionPixelSize(
                R.styleable.SelectableRoundedImageView_sriv_right_top_corner_radius, 0);
        mLeftBottomCornerRadius = a.getDimensionPixelSize(
                R.styleable.SelectableRoundedImageView_sriv_left_bottom_corner_radius, 0);
        mRightBottomCornerRadius = a.getDimensionPixelSize(
                R.styleable.SelectableRoundedImageView_sriv_right_bottom_corner_radius, 0);

        if (mLeftTopCornerRadius < 0.0f || mRightTopCornerRadius < 0.0f
                || mLeftBottomCornerRadius < 0.0f || mRightBottomCornerRadius < 0.0f) {
            throw new IllegalArgumentException("radius values cannot be negative.");
        }

        mRadii = new float[] { 
                mLeftTopCornerRadius, mLeftTopCornerRadius,
                mRightTopCornerRadius, mRightTopCornerRadius, 
                mRightBottomCornerRadius, mRightBottomCornerRadius, 
                mLeftBottomCornerRadius, mLeftBottomCornerRadius };

        mBorderWidth = a.getDimensionPixelSize(
                R.styleable.SelectableRoundedImageView_sriv_border_width, 0);
        if (mBorderWidth < 0) {
            throw new IllegalArgumentException("border width cannot be negative.");
        }

        mBorderColor = a
                .getColorStateList(R.styleable.SelectableRoundedImageView_sriv_border_color);
        if (mBorderColor == null) {
            mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
        }

        isOval = a.getBoolean(R.styleable.SelectableRoundedImageView_sriv_oval, false);
        a.recycle();

        updateDrawable();
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        invalidate();
    }

    @Override
    public ScaleType getScaleType() {
        return mScaleType;
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
        super.setScaleType(scaleType);
        mScaleType = scaleType;
        updateDrawable();
    }

    /*@Override
    public void setImageDrawable(Drawable drawable) {
        mResource = 0;
        mDrawable = SelectableRoundedCornerDrawable.fromDrawable(drawable, getResources());
        super.setImageDrawable(mDrawable);
        updateDrawable();
    }*/

    @Override
    public void setImageBitmap(Bitmap bm) {
        mResource = 0;
        mDrawable = SelectableRoundedCornerDrawable.fromBitmap(bm, getResources());
        super.setImageDrawable(mDrawable);
        updateDrawable();
    }

    @Override
    public void setImageResource(int resId) {
        if (mResource != resId) {
            mResource = resId;
            mDrawable = resolveResource();
            super.setImageDrawable(mDrawable);
            updateDrawable();
        }
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        setImageDrawable(getDrawable());
    }

    private Drawable resolveResource() {
        Resources rsrc = getResources();
        if (rsrc == null) {
            return null;
        }

        Drawable d = null;

        if (mResource != 0) {
            try {
                d = rsrc.getDrawable(mResource);
            } catch (NotFoundException e) {
                Log.w(TAG, "Unable to find resource: " + mResource, e);
                // Don't try again.
                mResource = 0;
            }
        }
        return SelectableRoundedCornerDrawable.fromDrawable(d, getResources());
    }

    private void updateDrawable() {
        if (mDrawable == null) {
            return;
        }

        ((SelectableRoundedCornerDrawable) mDrawable).setScaleType(mScaleType);
        ((SelectableRoundedCornerDrawable) mDrawable).setCornerRadii(mRadii);
        ((SelectableRoundedCornerDrawable) mDrawable).setBorderWidth(mBorderWidth);
        ((SelectableRoundedCornerDrawable) mDrawable).setBorderColor(mBorderColor);
        ((SelectableRoundedCornerDrawable) mDrawable).setOval(isOval);
    }

    public float getCornerRadius() {
        return mLeftTopCornerRadius;
    }

    /**
     * Set radii for each corner.
     * 
     * @param leftTop The desired radius for left-top corner in dip.
     * @param rightTop The desired desired radius for right-top corner in dip.
     * @param leftBottom The desired radius for left-bottom corner in dip.
     * @param rightBottom The desired radius for right-bottom corner in dip.
     * 
     */
    public void setCornerRadiiDP(float leftTop, float rightTop, float leftBottom, float rightBottom) {
        final float density = getResources().getDisplayMetrics().density;

        final float lt = leftTop * density;
        final float rt = rightTop * density;
        final float lb = leftBottom * density;
        final float rb = rightBottom * density;

        mRadii = new float[] { lt, lt, rt, rt, rb, rb, lb, lb };
        updateDrawable();
    }

    public float getBorderWidth() {
        return mBorderWidth;
    }

    /**
     * Set border width.
     * 
     * @param width
     *            The desired width in dip.
     */
    public void setBorderWidthDP(float width) {
        float scaledWidth = getResources().getDisplayMetrics().density * width; 
        if (mBorderWidth == scaledWidth) {
            return;
        }

        mBorderWidth = scaledWidth;
        updateDrawable();
        invalidate();
    }

    public int getBorderColor() {
        return mBorderColor.getDefaultColor();
    }

    public void setBorderColor(int color) {
        setBorderColor(ColorStateList.valueOf(color));
    }

    public ColorStateList getBorderColors() {
        return mBorderColor;
    }

    public void setBorderColor(ColorStateList colors) {
        if (mBorderColor.equals(colors)) {
            return;
        }

        mBorderColor = (colors != null) ? colors : ColorStateList
                .valueOf(DEFAULT_BORDER_COLOR);
        updateDrawable();
        if (mBorderWidth > 0) {
            invalidate();
        }
    }

    public boolean isOval() {
        return isOval;
    }

    public void setOval(boolean oval) {
        isOval = oval;
        updateDrawable();
        invalidate();
    }

    static class SelectableRoundedCornerDrawable extends Drawable {

        private static final String TAG = "SelectableRoundedCornerDrawable";
        private static final int DEFAULT_BORDER_COLOR = Color.BLACK;

        private RectF mBounds = new RectF();
        private RectF mBorderBounds = new RectF();

        private final RectF mBitmapRect = new RectF();
        private final int mBitmapWidth;
        private final int mBitmapHeight;

        private final Paint mBitmapPaint;
        private final Paint mBorderPaint;

        private BitmapShader mBitmapShader;

        private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 };
        private float[] mBorderRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 };

        private boolean mOval = false;

        private float mBorderWidth = 0;
        private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
        // Set default scale type to FIT_CENTER, which is default scale type of
        // original ImageView.
        private ScaleType mScaleType = ScaleType.FIT_CENTER;

        private Path mPath = new Path();
        private Bitmap mBitmap;
        private boolean mBoundsConfigured = false;

        public SelectableRoundedCornerDrawable(Bitmap bitmap, Resources r) {
            mBitmap = bitmap;
            mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

            if (bitmap != null) {
                mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics());
                mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics());
            } else {
                mBitmapWidth = mBitmapHeight = -1;
            }

            mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);

            mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBitmapPaint.setStyle(Paint.Style.FILL);
            mBitmapPaint.setShader(mBitmapShader);

            mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBorderPaint.setStyle(Paint.Style.STROKE);
            mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
            mBorderPaint.setStrokeWidth(mBorderWidth);
        }

        public static SelectableRoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) {
            if (bitmap != null) {
                return new SelectableRoundedCornerDrawable(bitmap, r);
            } else {
                return null;
            }
        }

        public static Drawable fromDrawable(Drawable drawable, Resources r) {
            if (drawable != null) {
                if (drawable instanceof SelectableRoundedCornerDrawable) {
                    return drawable;
                } else if (drawable instanceof LayerDrawable) {
                    LayerDrawable ld = (LayerDrawable) drawable;
                    final int num = ld.getNumberOfLayers();
                    for (int i = 0; i < num; i++) {
                        Drawable d = ld.getDrawable(i);
                        ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r));
                    }
                    return ld;
                }

                Bitmap bm = drawableToBitmap(drawable);
                if (bm != null) {
                    return new SelectableRoundedCornerDrawable(bm, r);
                } else {
                    Log.w(TAG, "Failed to create bitmap from drawable!");
                }
            }
            return drawable;
        }

        public static Bitmap drawableToBitmap(Drawable drawable) {
            if (drawable == null) {
                return null;
            }

            if (drawable instanceof BitmapDrawable) {
                return ((BitmapDrawable) drawable).getBitmap();
            }

            Bitmap bitmap;
            int width = Math.max(drawable.getIntrinsicWidth(), 2);
            int height = Math.max(drawable.getIntrinsicHeight(), 2);
            try {
                bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
                Canvas canvas = new Canvas(bitmap);
                drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                drawable.draw(canvas);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                bitmap = null;
            }
            return bitmap;
        }

        @Override
        public boolean isStateful() {
            return mBorderColor.isStateful();
        }

        @Override
        protected boolean onStateChange(int[] state) {
            int newColor = mBorderColor.getColorForState(state, 0);
            if (mBorderPaint.getColor() != newColor) {
                mBorderPaint.setColor(newColor);
                return true;
            } else {
                return super.onStateChange(state);
            }
        }

        private void configureBounds(Canvas canvas) {
            // I have discovered a truly marvelous explanation of this,
            // which this comment space is too narrow to contain. :)
            // If you want to understand what's going on here,
            // See http://www.joooooooooonhokim.com/?p=289
            Rect clipBounds = canvas.getClipBounds();
            Matrix canvasMatrix = canvas.getMatrix();

            if (ScaleType.CENTER == mScaleType) {
                mBounds.set(clipBounds);
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                applyScaleToRadii(canvasMatrix);
                mBounds.set(clipBounds);
            } else if (ScaleType.FIT_XY == mScaleType) {
                Matrix m = new Matrix();
                m.setRectToRect(mBitmapRect, new RectF(clipBounds), Matrix.ScaleToFit.FILL);
                mBitmapShader.setLocalMatrix(m);
                mBounds.set(clipBounds);
            } else if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType
                    || ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType) {
                applyScaleToRadii(canvasMatrix);
                mBounds.set(mBitmapRect);
            } else if (ScaleType.MATRIX == mScaleType) {
                applyScaleToRadii(canvasMatrix);
                mBounds.set(mBitmapRect);
            }
        }

        private void applyScaleToRadii(Matrix m) {
            float[] values = new float[9];
            m.getValues(values);
            for (int i = 0; i < mRadii.length; i++) {
                mRadii[i] = mRadii[i] / values[0];
            }
        }

        private void adjustCanvasForBorder(Canvas canvas) {
            Matrix canvasMatrix = canvas.getMatrix();
            final float[] values = new float[9];
            canvasMatrix.getValues(values);

            final float scaleFactorX = values[0];
            final float scaleFactorY = values[4];
            final float translateX = values[2];
            final float translateY = values[5];

            final float newScaleX = mBounds.width()
                    / (mBounds.width() + mBorderWidth + mBorderWidth);
            final float newScaleY = mBounds.height()
                    / (mBounds.height() + mBorderWidth + mBorderWidth);

            canvas.scale(newScaleX, newScaleY);
            if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType
                    || ScaleType.FIT_XY == mScaleType || ScaleType.FIT_CENTER == mScaleType
                    || ScaleType.CENTER_INSIDE == mScaleType || ScaleType.MATRIX == mScaleType) {
                canvas.translate(mBorderWidth, mBorderWidth);
            } else if (ScaleType.CENTER == mScaleType || ScaleType.CENTER_CROP == mScaleType) {
                // First, make translate values to 0
                canvas.translate(
                        -translateX / (newScaleX * scaleFactorX), 
                        -translateY / (newScaleY * scaleFactorY));
                // Then, set the final translate values.
                canvas.translate(-(mBounds.left - mBorderWidth), -(mBounds.top - mBorderWidth));
            } 
        }

        private void adjustBorderWidthAndBorderBounds(Canvas canvas) {
            Matrix canvasMatrix = canvas.getMatrix();
            final float[] values = new float[9];
            canvasMatrix.getValues(values);

            final float scaleFactor = values[0];

            float viewWidth = mBounds.width() * scaleFactor;
            mBorderWidth = (mBorderWidth * mBounds.width()) / (viewWidth - (2 * mBorderWidth));
            mBorderPaint.setStrokeWidth(mBorderWidth);

            mBorderBounds.set(mBounds);
            mBorderBounds.inset(- mBorderWidth / 2, - mBorderWidth / 2);
        }

        private void setBorderRadii() {
            for (int i = 0; i < mRadii.length; i++) {
                if (mRadii[i] > 0) {
                    mBorderRadii[i] = mRadii[i];
                    mRadii[i] = mRadii[i] - mBorderWidth;
                }
            }
        }

        @Override
        public void draw(Canvas canvas) {
            canvas.save();
            if (!mBoundsConfigured) {
                configureBounds(canvas);
                if (mBorderWidth > 0) {
                    adjustBorderWidthAndBorderBounds(canvas);
                    setBorderRadii();
                }
                mBoundsConfigured = true;
            }

            if (mOval) {
                if (mBorderWidth > 0) {
                    adjustCanvasForBorder(canvas);
                    mPath.addOval(mBounds, Path.Direction.CW);
                    canvas.drawPath(mPath, mBitmapPaint);
                    mPath.reset();
                    mPath.addOval(mBorderBounds, Path.Direction.CW);
                    canvas.drawPath(mPath, mBorderPaint);
                } else {
                    mPath.addOval(mBounds, Path.Direction.CW);
                    canvas.drawPath(mPath, mBitmapPaint);
                }
            } else {
                if (mBorderWidth > 0) {
                    adjustCanvasForBorder(canvas);
                    mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW);
                    canvas.drawPath(mPath, mBitmapPaint);
                    mPath.reset();
                    mPath.addRoundRect(mBorderBounds, mBorderRadii, Path.Direction.CW);
                    canvas.drawPath(mPath, mBorderPaint);
                } else {
                    mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW);
                    canvas.drawPath(mPath, mBitmapPaint);
                }
            }
            canvas.restore();
        }

        public void setCornerRadii(float[] radii) {
            if (radii == null)
                return;

            if (radii.length != 8) {
                throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
            }

            for (int i = 0; i < radii.length; i++) {
                mRadii[i] = radii[i];
            }
        }

        @Override
        public int getOpacity() {
            return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT
                    : PixelFormat.OPAQUE;
        }

        @Override
        public void setAlpha(int alpha) {
            mBitmapPaint.setAlpha(alpha);
            invalidateSelf();
        }

        @Override
        public void setColorFilter(ColorFilter cf) {
            mBitmapPaint.setColorFilter(cf);
            invalidateSelf();
        }

        @Override
        public void setDither(boolean dither) {
            mBitmapPaint.setDither(dither);
            invalidateSelf();
        }

        @Override
        public void setFilterBitmap(boolean filter) {
            mBitmapPaint.setFilterBitmap(filter);
            invalidateSelf();
        }

        @Override
        public int getIntrinsicWidth() {
            return mBitmapWidth;
        }

        @Override
        public int getIntrinsicHeight() {
            return mBitmapHeight;
        }

        public float getBorderWidth() {
            return mBorderWidth;
        }

        public void setBorderWidth(float width) {
            mBorderWidth = width;
            mBorderPaint.setStrokeWidth(width);
        }

        public int getBorderColor() {
            return mBorderColor.getDefaultColor();
        }

        public void setBorderColor(int color) {
            setBorderColor(ColorStateList.valueOf(color));
        }

        public ColorStateList getBorderColors() {
            return mBorderColor;
        }

        /**
         * Controls border color of this ImageView.
         * 
         * @param colors
         *            The desired border color. If it's null, no border will be
         *            drawn.
         * 
         */
        public void setBorderColor(ColorStateList colors) {
            if (colors == null) {
                mBorderWidth = 0;
                mBorderColor = ColorStateList.valueOf(Color.TRANSPARENT);
                mBorderPaint.setColor(Color.TRANSPARENT);
            } else {
                mBorderColor = colors;
                mBorderPaint.setColor(mBorderColor.getColorForState(getState(),
                        DEFAULT_BORDER_COLOR));
            }
        }

        public boolean isOval() {
            return mOval;
        }

        public void setOval(boolean oval) {
            mOval = oval;
        }

        public ScaleType getScaleType() {
            return mScaleType;
        }

        public void setScaleType(ScaleType scaleType) {
            if (scaleType == null) {
                return;
            }
            mScaleType = scaleType;
        }
    }

}

动用时,将它设置在view
的背景上,有的同学如此问,如下使用shape,为啥不起效用?第一例,
不起成效:

绘制到DecerView上

咱俩用队列举行多个辅导层的军管,贰遍性将所要求显示并切换的图层都增加进去

Queue<View> mQueue;View currentView;Activity context;public ShowCaseView(Activity context) { this.context = context;}public void addViews(ArrayList<View> views) { mQueue=new LinkedList<>(); mQueue.addAll;}

然后正是加多跟移除,这里每一趟移除完之后都会判断队列中一旦还会有未出示的,会随着继继续展览示出来

public void show() { if (!mQueue.isEmpty { currentView=mQueue.poll(); ((ViewGroup) context.getWindow().getDecorView.addView(currentView); }}public void dismiss() { if (currentView!=null) { ((ViewGroup) context.getWindow().getDecorView.removeView(currentView); } show();}

再有一种情景正是直接全体跳过

public void cancel() { if (!mQueue.isEmpty { mQueue.clear(); } if (currentView!=null) { ((ViewGroup) context.getWindow().getDecorView.removeView(currentView); }}

attrs.xml资源:

 <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/round_corner_rectangle" android:scaleType="fitXY" android:src="@drawable/img"/>

标准使用

要是取得到高亮提醒的控件的坐标,就能够对其开展高亮管理

public View a() { ArrayList<CalculatorBean> beanArrayList=new ArrayList<>(); int[] location=new int[2]; btn_showcase.getLocationOnScreen; CalculatorBean bean=new CalculatorBean(); bean.setmCircleCenterX(location[0]+btn_showcase.getMeasuredWidth; bean.setmCircleCenterY(location[1]+btn_showcase.getMeasuredHeight; bean.setmCircleRadius; bean.setmFocusShape(FocusShape.CIRCLE); beanArrayList.add; View view= LayoutInflater.from(ShowcaseActivity.this).inflate(R.layout.view_showcase, null, false); ShowCaseImageView image_showcase= view.findViewById(R.id.image_showcase); image_showcase.setmAnimationEnabled; image_showcase.setmCalculatorBeen(beanArrayList); image_showcase.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showCaseView.dismiss; return view;}
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="SelectableRoundedImageView">
        <attr name="sriv_left_top_corner_radius" format="dimension" />
        <attr name="sriv_right_top_corner_radius" format="dimension" />
        <attr name="sriv_left_bottom_corner_radius" format="dimension" />
        <attr name="sriv_right_bottom_corner_radius" format="dimension" />
        <attr name="sriv_border_width" format="dimension" />
        <attr name="sriv_border_color" format="color" />
        <attr name="sriv_oval" format="boolean" />
        <attr name="android:scaleType" />
    </declare-styleable>

</resources>

第二例,不起功效,看不到圆角效用

为了方便扩张,完毕通过代码和xml属性自由支配各类属性,所以源码中的getter和setter方法很多,导致源码较长。通过阅读源码,鸟哥总计了以下多少个要点部分,帮助大家知道源码。

<FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/round_corner_rectangle"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /></FrameLayout>
  • 消失的onDraw方法
    看完全体源码,会意识,未有自定义View非凡的onDraw绘制方法(将促成在布局中不能够预览自定义分界面效果,那也是其一开源库的二个欠缺吧,我们感兴趣的话能够在源码的根底上海展览中心开修改完成)。而是经过自定义Drawable类——SelectableRoundedCornerDrawable,对ImageView的drawable实行圆角和圆形管理,再通过ImageView的setImageDrawable等种类API传递给ImageView。

  • BitmapShader给画笔着色
    自定义View达成圆形或圆角的措施有各种,常见的举例说 Paint.setXfermode
    Paint.setShader(BitmapShader)
    ,鸟哥前边介绍几篇文章如【ImageView】自定义ImageView种类(一)——简单圆形图片中用到的是前面多个,本文中介绍的
    SelectableRoundedImageView
    用到的是后人。使用着色器BitmapShader给绘制图片的画笔着色,通过画笔体现图片。

  • 关闭的圆形或圆角Path
    在自定义Drawable内部类中,重写 draw(Canvas canvas) 方法,通过
    canvas.drawPath
    方法运用被BitmapShader着色器着色过的Paint绘制闭合路线。源码中采用三个布尔型变量mOval调节是还是不是绘制圆形图片,使用
    Path.addOval(RectF oval, Direction dir) 增加圆形路线或许
    Path.addRoundRect(RectF rect, float[] radii, Direction dir)
    方法增多圆角矩形路线。

第三例,TextView 有圆角,正常

摸底以上几点,基本上能驾驭 SelectableRoundedImageView
核心的落成原理。通过缩放格局和一些几何运算,便足以测算出边框的宽窄、圆角的半径、图片的分寸等,这里不细作剖析,查看源码就可以。

<TextView android:background="@drawable/round_corner_rectangle" android:layout_height="wrap_content" android:layout_width="wrap_content" />

在布局中选拔:

以圆角矩形<shape>为例,在那之中 shape 标签在条分缕析后对应于
GradientDrawable类(注意不是ShapeDrawable),即在xml里定义<shape>,运转时期会生成对应的GradientDrawable对象,同不平时间传入xml里定义的圆角属性值。

<com.joooonho.SelectableRoundedImageView
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/image"
        android:src="@drawable/photo1"
        android:scaleType="centerCrop"
        app:sriv_left_top_corner_radius="16dip"
        app:sriv_right_top_corner_radius="0dip"
        app:sriv_left_bottom_corner_radius="48dip"
        app:sriv_right_bottom_corner_radius="16dip"
        app:sriv_border_width="2dip"
        app:sriv_border_color="#008fea"
        app:sriv_oval="true" />

翻看GradientDrawable
源码,将见到在xml里<shape>设定的各种角圆角弧度,被传到并保留在数组mRadiusArray:

在代码中运用:

private void updateDrawableCorners(TypedArray a) {...... setCornerRadii(new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius });}
SelectableRoundedImageView sriv = new SelectableRoundedImageView(context);
sriv.setScaleType(ScaleType.CENTER_CROP);
sriv.setCornerRadiiDP(4, 4, 0, 0);
sriv.setBorderWidthDP(4);
sriv.setBorderColor(Color.BLUE);
sriv.setImageDrawable(drawable);
sriv.setOval(true);

由此,设定shape标签即设生成drawable 对象。

亟需注意的一点是,假使利用Android图片加载开源框架
Android-Universal-Image-Loader
,确定保证在布局 DisplayImageOptions 参数时利用 SimpleBitmapDisplayer
或者 FadeInBitmapDisplayer,而不是 RoundedBitmapDisplayer 或者
RoundedVignetteBitmapDisplayer,如:

  • Drawable
    能够领会为:二维平面上,能画出来的图形图像,如:BitmapDrawable,
    ShapeDrawable, PictureDrawable, LayerDrawable, 等等派生类。Drawable
    都有本人的draw() 方法,来垄断 canvas
  • canvas
    画布是透明的,能够在上头涂抹任性形状,并填充上颜色、渐变等,即
    Drawable
options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.ic_stub)
                .showImageForEmptyUri(R.drawable.ic_empty)
                .showImageOnFail(R.drawable.ic_error)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .considerExifParams(true)
//              .displayer(new RoundedBitmapDisplayer(20))
//              DO NOT USE RoundedBitmapDisplayer. Use SimpleBitmapDisplayer!
                .displayer(new SimpleBitmapDisplayer())
                .build();

三番五次查看GradientDrawable源码,其绘制进度是骨干图形绘制,涉及:Canvas、Path、
Paint。个中path 定义密闭形状,并设定好圆角,paint
画笔设置颜色等,最后在canvas 画布上画出图形,步骤如下:


  • path定义密封形状代码如下:

应接关切鸟哥微信公众号【技艺鸟】,一齐享受,一齐学学!

微信徒人号【技术鸟】.gif

 private void buildPathIfDirty() { final GradientState st = mGradientState; if (mPathIsDirty) { ensureValidRect(); mPath.reset(); mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); mPathIsDirty = false; } }
  • 画线及填充,558行:

switch (st.mShape) { case RECTANGLE: if (st.mRadiusArray != null) { buildPathIfDirty(); // 画线及填充 canvas.drawPath(mPath, mFillPaint); if (haveStroke) { // 描边 canvas.drawPath(mPath, mStrokePaint); } }

如上深入分析了概念二个 圆角矩形时,GradientDrawable 将在canvas上作者绘制的进程。

以第三例为例,设置TextView的background,先精通以下基础:

  1. TextView 继承自View基类
  2. 安装各类背景都将转向为drawable对象
  3. View 里有两个公用画布 canvas

翻看View源码,View 里背景和内容的绘图步骤:

  1. 首先绘制底部 background
  2. 绘图具体的源委,通过onDraw 文告承袭View类子类绘制具体内容。

 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 的源码及注释 16153 行: /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground; } // 接着,绘制内容,dispatchDraw 通知该 View 上的子结点进行自我绘制。 // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw; // Step 4, draw the children dispatchDraw; // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty { mOverlay.getOverlayView().dispatchDraw; } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground; // we're done... return; }

绘制Background 的进度,简化一下即为drawable 间接调用本身 draw
方法,在同一画布上张开绘图。

 private void drawBackground(Canvas canvas) { final Drawable background = mBackground;… background.draw; }

上述剖析表达了View绘制背景和内容的分别,相同的时间,也是有意照旧无意能够表达Imageview 的
background 和 src 的不相同之处:

  1. 实为上无区别,都以各类不一致品种的drawable,本质上都通过自个儿的draw方法在canvas上制图。
  2. background 是背景,首先会在View基类的draw里被绘制。src
    是内容,随后在子类ImageView的 ondraw 里被绘制。

此地说飞鹤下,要是将 android:background=“@null” 会发生哪些

 <Button android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:text="此处背景透明" android:layout_alignParentBottom="true"/>

会发觉,将会取和装置 transparent 也是一模二样晶莹的作用。

将会看到,是和设置背景为 transparent 同样晶莹的遵守。

回头来看文中开端处涉及的shape不起成效的例子:第二例在那之中TextView为外界FrameLayout
的子结点,外部FrameLayout设置的的标签与TextView非亲非故,TextView的绘图范围仅宽高受FrameLayout的震慑,标签只表示了一个图像,不影响子节点。解决措施:FrameLayout
设置padding, 或然TextView设置
margin,padding要压倒等于sqrt,个中r为所设圆角半径值,何况两个背景颜色同样。为什么为sqrt,请自行画图计算。

第一例ImageView 设置圆角为何不起作用。参见 ImageView 里源码,src
对应 mDrawable,绘制时,将掩饰底层 background,即设置了圆角的drawable。

 private void updateDrawable(Drawable d) {…… mDrawable = d;} @Override protected void onDraw(Canvas canvas) { super.onDraw;… mDrawable.draw; }

消除办法那该怎么给ImageView 画圆角呢?办法是通过paint
的SRC_IN模式:paint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

SRC_IN
方式设置后,将八个绘制的职能叠合后取交集后表现,比如:第三个绘制的是个圆形,第4个绘制的是个Bitmap,于是交集为圆形,就贯彻了圆形图片效果。

而且,android Tint 也是靠 SRC_IN
来机关成为大家想要的背景颜色,来完成Material Design的功用。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website