Personal blog

www.milovetingting.cn

Android custom View- round picture control

preface

In daily development, circular image effects are still very common. This can be done by setting Xfermode for Paint, as briefly noted below.

implementation

The core to achieve the circular effect is PorterDuffXfermode. For PorterDuffXfermode, it is not expanded here and relevant information can be queried.

The core code

// Draw the background
canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2, mPaint);
// Set the mode to: display the intersection of the background layer and the upper layer, and display the upper layer image
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// Draw the image to display
canvas.drawBitmap(mSrcBitmap, 0.0, mPaint);
/ / reset Xfermode
mPaint.setXfermode(null);
Copy the code

Custom attributes

<?xml version="1.0" encoding="utf-8"? >
<resources>
    <declare-styleable name="CircleView">
        <! -- Define resources -->
        <attr name="src" format="reference" />
        <! -- Define type -->
        <attr name="type" format="enum">
            <! -- the round -- -- >
            <enum name="round" value="1" />
            <! - rectangular - >
            <enum name="rect" value="2" />
        </attr>
    </declare-styleable>
</resources>
Copy the code

Custom control

public class CircleView extends View {

    private static final int DEFAULT_SIZE = 200;

    private static final int DEFAULT_RADIUS = 20;

    private static final int TYPE_ROUND = 1;

    private static final int TYPE_RECT = 2;

    private int mSize;

    private int mResourceId;

    private int mType;

    private Paint mPaint;

    private Bitmap mSrcBitmap;

    public CircleView(Context context) {
        this(context, null);
    }

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

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mResourceId = ta.getResourceId(R.styleable.CircleView_src, R.mipmap.ic_launcher);
        mType = ta.getInt(R.styleable.CircleView_type, TYPE_ROUND);
        ta.recycle();
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = getMeasureSize(widthMeasureSpec);
        int height = getMeasureSize(heightMeasureSpec);
        mSize = Math.min(width, height);
        setMeasuredDimension(mSize, mSize);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Draw the background
        if (mSrcBitmap == null) {
            mSrcBitmap = getScaleBitmap();
        }
        if (mType == TYPE_ROUND) {
            canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2, mPaint);
        } else if (mType == TYPE_RECT) {
            canvas.drawRoundRect(0.0, mSize, mSize, DEFAULT_RADIUS, DEFAULT_RADIUS, mPaint);
        }
        // Set the mode to: display the intersection of the background layer and the upper layer, and display the upper layer image
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        // Draw the image to display
        canvas.drawBitmap(mSrcBitmap, 0.0, mPaint);
        / / reset Xfermode
        mPaint.setXfermode(null);
    }

    private void init(a) {
        // Disable hardware acceleration, otherwise circles may not be drawn
        setLayerType(LAYER_TYPE_HARDWARE, null);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    private int getMeasureSize(int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        return mode == MeasureSpec.EXACTLY ? size : DEFAULT_SIZE;
    }

    /** * Get the scaled Bitmap **@return* /
    private Bitmap getScaleBitmap(a) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), mResourceId, options);
        options.inSampleSize = calcSampleSize(options, mSize, mSize);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), mResourceId, options);
    }

    /** * Computes the scale **@param option
     * @param width
     * @param height
     * @return* /
    private int calcSampleSize(BitmapFactory.Options option, int width, int height) {
        int originWidth = option.outWidth;
        int originHeight = option.outHeight;
        int sampleSize = 1;
        while ((originWidth = originWidth >> 1) > width && (originHeight = originHeight >> 1) > height) {
            sampleSize = sampleSize << 1;
        }
        returnsampleSize; }}Copy the code

Note: If there is no circular effect, then hardware acceleration may need to be disabled: setLayerType(LAYER_TYPE_HARDWARE, null)

layout

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.wangyz.custom.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        app:src="@drawable/image" />

    <com.wangyz.custom.CircleView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        app:src="@drawable/image" />

    <com.wangyz.custom.CircleView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        app:src="@drawable/image"
        app:type="rect" />

</LinearLayout>
Copy the code

The effect