Zero, Preface:

Summary of knowledge points in this paper

1. You will also have a deeper understanding of onMesure control and custom property writing method

2. Simple processing of bitmap and canvas area clipping 3. This article implements two custom controls :FitImageView(image adaptive) and BiggerView(magnifying glass), with the former as a warm-up for the latter. 4. Finally, I will introduce how to generate your own dependency library from Guihub, so that a complete custom control library is OK. 5. The source code of this project is shown in article 1 at the end of the document

Implementation effect list:

1. Magnifier Effect 1:

2. Magnifier effect 2 :(using clipOutPath requires API26)

3. The control has made a class library (welcome star), use:
allprojects { repositories { ... Dependencies maven {url 'https://jitpack.io'}}} {implementation 'com. Making. Toly1994328: BiggerView: v1.01'}Copy the code

First, width and height ratio adaptive control: FitImageView

At first, I wanted to create a magnifying glass effect, but I inherited ImageView without thinking too much. As it got harder, the cropping mode of bitmap affected the size of the image displayed in the view.

The size of the View itself will not change, which will make it difficult to capture the width and height of the image, and the upper left corner of the image. This will lead to difficult positioning when drawing enlarged images, so many cropping patterns, think about the crash. So I think, I define the image display view to calculate, the requirements are proportional to the width and height, and the size of the view is the size of the picture, the blue as the background, the results are as follows, you should understand what is meant, the image is not deformation, but do not want to exceed the background area:


1. Custom attributes:
<! < Declare -styleable name="FitImageView"> <! < declare-styleable> < declare-styleable> < declare-styleable>Copy the code
2. Customize the control initialization code
/** * Author: Zhang Feng Jiete Li <br/> * Time: 2018/11/190019:0:14 <br/> * Email: [email protected]<br/> * Description: Public class FitImageView extends View {private Paint mPaint; Private Drawable mFitSrc; Drawable private Bitmap mBitmapSrc; Drawable private Bitmap mBitmapSrc; // Protected Bitmap mFitBitmap; // Protected float scaleRateW2fit = 1; Protected float scaleRateH2fit = 1; // protected int mImageW, mImageH; Public FitImageView(Context Context) {this(Context, null); } public FitImageView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public FitImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FitImageView); mFitSrc = a.getDrawable(R.styleable.FitImageView_z_fit_src); a.recycle(); init(); Private void init() {// initialize the main paintbrush mPaint = new Paint(paint.anti_alias_flag); mBitmapSrc = ((BitmapDrawable) mFitSrc).getBitmap(); } @override protected void onDraw(Canvas) {super.ondraw (Canvas); //TODO draw }Copy the code
3. Measurement and placement :(this is the core processing)
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mImageW = dealWidth(widthMeasureSpec); MImageH = dealHeight(heightMeasureSpec); Float bitmapWHRate = mbitmapsrc.getheight () * 1.f/mbitmapsrc.getwidth (); float bitmapWHRate = mbitmapsrc.getheight () * 1.f/mbitmapsrc.getwidth (); If (mImageH >= mImageW) {mImageH = (int) (mImageW * bitmapWHRate); } else {mImageW = (int) (mImageH/bitmapWHRate); } Set MeasureDimension (mImageW, mImageH); } /** * @param heightMeasureSpec * @return */ private int dealHeight(int heightMeasureSpec) { int result = 0; int mode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); If (mode == measurespec.exactly) {// control size is already set: // android:layout_height="40dp" or "match_parent" scaleRateH2fit = size * 1.f/mbitmapsrc.getheight () * 1.f; // Android :layout_height="40dp" or "match_parent" scaleRateH2fit = size * 1.f/mBitmapSrc. result = size; } else { result = mBitmapSrc.getHeight(); If (mode == measurespec.at_most) {result = math.min (result, size); } } return result; } /** * @param widthMeasureSpec */ private int dealWidth(int widthMeasureSpec) { int result = 0; int mode = MeasureSpec.getMode(widthMeasureSpec); int size = MeasureSpec.getSize(widthMeasureSpec); If (mode == measurespec.exactly) {// control size is already set: // android:layout_XXX="40dp" or "match_parent" scaleRateW2fit = size * 1.f/mbitmapsrc.getwidth (); // android:layout_XXX="40dp" or "match_parent" scaleRateW2fit = size * 1.f/mBitmapSrc. result = size; } else { result = mBitmapSrc.getWidth(); If (mode == measurespec.at_most) {result = math.min (result, size); } } return result; }Copy the code
4. Create the scaled bitmap and draw it

The timing of the creation is in onLayout because the scale ratio needs to be measured first

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mFitBitmap = createBigBitmap(Math.min(scaleRateW2fit, scaleRateH2fit), mBitmapSrc); mBitmapSrc = null; } /** * Create an image with a rate multiple ** @param rate zoom ratio * @param SRC Image source * @return zoom image */ protected Bitmap createBigBitmap(float rate, Bitmap src) { Matrix matrix = new Matrix(); Matrix.setvalues (new float[]{rate, 0, 0, 0, rate, 0, 0, 0, 1}); return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(mFitBitmap, 0, 0, mPaint); }Copy the code

One, custom control: BiggerView

1. Customize attributes: attrs.xml
<? The XML version = "1.0" encoding = "utf-8"? > <resources> <! <declare-styleable name="BiggerView"> <! - radius - > < attr name = "z_bv_radius format =" dimension "/" > <! <attr name="z_bv_outline_width" format="dimension"/> <! - color - > < attr name = "z_bv_outline_color format =" "color" / > <! < declare-styleable> </declare-styleable>Copy the code
2. Initialize the custom control
public class BiggerView extends FitImageView { private int mBvRadius = dp(30); Private int mBvOutlineWidth = 2; Private float rate = 4; Private int mBvOutlineColor = 0xffCCDCE4; private int mBvOutlineColor = 0xffCCDCE4; // Private Paint mPaint; // Private Bitmap mBiggerBitmap; // Private Path mPath; Public BiggerView(Context Context) {this(Context, null); } public BiggerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BiggerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BiggerView); mBvRadius = (int) a.getDimension(R.styleable.BiggerView_z_bv_radius, mBvRadius); mBvOutlineWidth = (int) a.getDimension(R.styleable.BiggerView_z_bv_outline_width, mBvOutlineWidth); mBvOutlineColor = a.getColor(R.styleable.BiggerView_z_bv_outline_color, mBvOutlineColor); rate = (int) a.getFloat(R.styleable.BiggerView_z_bv_rate, rate); a.recycle(); init(); } private void init() {// initialize the main paintbrush mPaint = new Paint(paint.anti_alias_flag); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mBvOutlineColor); mPaint.setStrokeWidth(mBvOutlineWidth * 2); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }}}Copy the code

Second, the primary stage

When you click, a ball will be generated, and it will move along with your finger, and disappear when you release your hand, as shown in the figure below:

This ball is where the local magnification will be shown

1. Add member variables:
private int mBvRadius = dp(30); // private Paint mPaint; Private float mCurX; Private float mCurY; private float mCurY; Y private Boolean isDown; // Whether to touchCopy the code
2. Handling of contacts
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: isDown = true; mCurX = event.getX(); mCurY = event.getY(); break; case MotionEvent.ACTION_UP: isDown = false; } invalidate(); Return true; }Copy the code
3. Draw
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isDown) { canvas.drawCircle(mCurX, mCurY, mBvRadius, mPaint); }}Copy the code

Iii. Intermediate stage :(enlarged image processing)

1. Create a Bitmap on onLayout that is multiple of the rate
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    mBiggerBitmap = createBigBitmap(rate, mFitBitmap);
}
Copy the code
2. Draw the enlarged diagram

Here, by positioning, you move the image to the specified position

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isDown) { canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), -mCurY * (rate - 1), mPaint); }}Copy the code

So effect 1 is done


3. Realization of Effect 2:

Use the clipOutPath API, not required to be 26 or higher

The first contact was in the center of the circle, and it was moved up here (for the simple reason that the finger was too big and it obscured the area to be seen…). The problem with this is that the top part cannot be displayed further up, and the optimization is as follows:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    mShowY = -mCurY * (rate - 1) - 2 * mBvRadius;
    canvas.drawBitmap(mBiggerBitmap,
            -mCurX * (rate - 1), mShowY, mPaint);
    float rY = mCurY > 2 * mBvRadius ? mCurY - 2 * mBvRadius : mCurY +  mBvRadius;
    mPath.addCircle(mCurX, rY, mBvRadius, Path.Direction.CCW);
    canvas.clipOutPath(mPath);
    super.onDraw(canvas);
    canvas.drawCircle(mCurX, rY, mBvRadius, mPaint);
}
Copy the code

Iv. Advanced stage: Optimization points:

1. Use enumeration to switch magnifier type:
Enum Style {NO_CLIP,// no clipping, } @override protected void onDraw(Canvas) {super.ondraw (Canvas); If (isDown) {switch (mStyle) {case NO_CLIP:// float showY = -mcury * (rate-1); canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), showY, mPaint); break; case CLIP_CIRCLE: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mPath.reset(); showY = -mCurY * (rate - 1) - 2 * mBvRadius; canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), showY, mPaint); float rY = mCurY > 2 * mBvRadius ? mCurY - 2 * mBvRadius : mCurY + mBvRadius; mPath.addCircle(mCurX, rY, mBvRadius, Path.Direction.CCW); canvas.clipOutPath(mPath); super.onDraw(canvas); canvas.drawCircle(mCurX, rY, mBvRadius, mPaint); } else { mStyle = Style.NO_CLIP; // If the version is too low, no cropping, zoom in invalidate(); } // More modes can be extended.... }}}Copy the code
2. Processing of the drop point in the image boundary region:

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: mCurX = event.getX(); mCurY = event.getY(); IsDown = judgeRectArea(mImageW / 2, mImageH / 2, mCurX, mCurY, mImageW, mImageH); break; case MotionEvent.ACTION_UP: isDown = false; } invalidate(); Return true; */ public static Boolean judgeRectArea(float srcX, float srcY, float dstX, float dstY, float w, float dstX) float h) { return Math.abs(dstX - srcX) < w / 2 && Math.abs(dstY - srcY) < h / 2; }Copy the code

Upload to Github and build it into a library

0. Become a library!! , become a library!! , become a library!!


1. Upload a lot


2. Release:


3. See:jitpack.io/

4. Test usage:

Ok, the end of this article


Postscript: Jie wen standard

1. Growth record and Errata of this paper
Program source code The date of note
V0.1 – making 2018-11-17 BiggerView is a local image magnifier for Android custom controls
2. More about me
Pen name QQ WeChat hobby
Zhang Feng Jie te Li 1981462002 zdl1994328 language
My lot My Jane books I’m the nuggets Personal website
3. The statement

1—- This article is originally written by Zhang Fengjie, please note if reproduced

2—- welcome the majority of programming enthusiasts to communicate with each other 3—- personal ability is limited, if there is something wrong welcome to criticize and testify, must be humble to correct 4—- see here, I thank you here for your love and support