Android自定義控制元件之區域性圖片放大鏡–BiggerView

張風捷特烈發表於2018-11-19

零、前言:

本文的知識點一覽

1.自定義控制元件及自定義屬性的寫法,你也將對onMesure有更深的認識
2.關於bitmap的簡單處理,及canvas區域裁剪
3.本文會實現兩個自定義控制元件:FitImageView(圖片自適應)BiggerView(放大鏡),前者為後者作為鋪墊。
4.最後會介紹如何從guihub生成自己的依賴庫,這樣一個完整的自定義控制元件庫便ok了。
5.本專案原始碼見文尾捷文規範第一條

實現效果一覽:

1.放大鏡效果1:

放大鏡效果1.gif

2.放大鏡效果2:(使用了clipOutPath需要API26)

放大鏡效果2.gif
3.該控制元件已做成類庫(歡迎star),使用:
 allprojects { 
repositories {
... maven {
url 'https://jitpack.io'
}
}
} dependencies {
implementation 'com.github.toly1994328:BiggerView:v1.01'
}複製程式碼

一、寬高等比例自適應的控制元件:FitImageView

一開始想做放大鏡效果,沒多想就繼承ImageView了,後來越做越困難,bitmap的裁剪模式會影響檢視中顯示圖片的大小。
而View自己的的大小不變,會導致圖片顯示寬高捕捉困難,和圖片左上角捕捉困難。
這就會導致繪製放大圖片時的定位適配困難,那麼多裁剪模式,想想都崩潰。
於是我想到,自己定義影像顯示的view算了,需求是寬高按比例適應,並且View的尺寸即圖片的尺寸,
將藍色作為背景,結果如下,你應該明白是什麼意思了吧,就是既想要圖片不變形,又想不要超出的背景區域:

寬大於高.png
高大於寬.png

1.自定義屬性:
    <
!--圖片放大鏡-->
<
declare-styleable name="FitImageView">
<
!--圖片資源-->
<
attr name="z_fit_src" format="reference"/>
<
/declare-styleable>
複製程式碼
2.自定義控制元件初始程式碼
/** * 作者:張風捷特烈<
br/>
* 時間:2018/11/19 0019:0:14<
br/>
* 郵箱:1981462002@qq.com<
br/>
* 說明:寬高自適應圖片檢視 */public class FitImageView extends View {
private Paint mPaint;
//主畫筆 private Drawable mFitSrc;
//自定義屬性獲取的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() {
//初始化主畫筆 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBitmapSrc = ((BitmapDrawable) mFitSrc).getBitmap();
//獲取圖片
} @Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//TODO draw
}複製程式碼
3.測量及擺放:(這是核心處理)
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mImageW = dealWidth(widthMeasureSpec);
//顯示圖片寬 mImageH = dealHeight(heightMeasureSpec);
//顯示圖片高 float bitmapWHRate = mBitmapSrc.getHeight() * 1.f / mBitmapSrc.getWidth();
//圖片寬高比 if (mImageH >
= mImageW) {
mImageH = (int) (mImageW * bitmapWHRate);
//寬小,以寬為基準
} else {
mImageW = (int) (mImageH / bitmapWHRate);
//高小,以高為基準
} setMeasuredDimension(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) {
//控制元件尺寸已經確定:如: // android:layout_height="40dp""match_parent" scaleRateH2fit = size * 1.f / mBitmapSrc.getHeight() * 1.f;
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) {
//控制元件尺寸已經確定:如: // android:layout_XXX="40dp""match_parent" scaleRateW2fit = size * 1.f / mBitmapSrc.getWidth();
result = size;

} else {
result = mBitmapSrc.getWidth();
if (mode == MeasureSpec.AT_MOST) {//最多不超過 result = Math.min(result, size);

}
} return result;

}複製程式碼
4.建立縮放後的bitmap及繪製

建立的時機選擇在onLayout裡,因為要先測量後才能知道縮放比

@Overrideprotected 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;
//原圖已無用將原圖置空
}/** * 建立一個rate倍的圖片 * * @param rate 縮放比率 * @param src 圖片源 * @return 縮放後的圖片 */protected Bitmap createBigBitmap(float rate, Bitmap src) {
Matrix matrix = new Matrix();
//設定變換矩陣:擴大3倍 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);

}@Overrideprotected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mFitBitmap, 0, 0, mPaint);

}複製程式碼

一、自定義控制元件:BiggerView

1.自定義屬性:attrs.xml
<
?xml version="1.0" encoding="utf-8"?>
<
resources>
<
!--圖片放大鏡-->
<
declare-styleable name="BiggerView">
<
!--半徑-->
<
attr name="z_bv_radius" format="dimension"/>
<
!--邊線寬-->
<
attr name="z_bv_outline_width" format="dimension"/>
<
!--進度色-->
<
attr name="z_bv_outline_color" format="color"/>
<
!--放大倍率-->
<
attr name="z_bv_rate" format="float"/>
<
/declare-styleable>
<
/resources>
複製程式碼
2.初始化自定義控制元件
public class BiggerView extends FitImageView { 
private int mBvRadius = dp(30);
//半徑 private int mBvOutlineWidth = 2;
//邊線寬 private float rate = 4;
//預設放大的倍數 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() {
//初始化主畫筆 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);

}
}
}複製程式碼

二、初級階段

點選的時候生成一個圓球,並隨著手指移動跟隨移動,鬆開手時消失,如圖:
這個小球就是將來展示區域性放大效果的地方

初階效果.gif
1.新增成員變數:
private int mBvRadius = dp(30);
//半徑private Paint mPaint;
//主畫筆private float mCurX;
//當前觸點Xprivate float mCurY;
//當前觸點Yprivate boolean isDown;
//是否觸控複製程式碼
2.觸點的處理
@Overridepublic 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;

}複製程式碼
3.繪製
@Overrideprotected void onDraw(Canvas canvas) { 
super.onDraw(canvas);
if (isDown) {
canvas.drawCircle(mCurX, mCurY, mBvRadius, mPaint);

}
}複製程式碼

三、中級階段:(放大圖片的處理)

放大鏡效果1.gif
放大圖平移到觸點.png
1.在onLayout時建立一個rate倍大小的Bitmap
@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) { 
super.onLayout(changed, left, top, right, bottom);
mBiggerBitmap = createBigBitmap(rate, mFitBitmap);

}複製程式碼
2.繪製比放大後的圖

這裡通過定位,將圖片移至指定位置

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

}
}複製程式碼

這樣效果1就完成了


3.效果2的實現:

使用了clipOutPath的API,不須26及以上
一開始觸點是在圓的中心,這裡往上調了一下(理由很簡單,手指太大,把要看的部位遮住了…)
但這有個問題,就是最上面的部分再往上就無法顯示了,使用做了如下的優化:

優化.gif
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);

}複製程式碼

四、高階階段:優化點:

1.使用列舉切換放大鏡型別:
enum Style { 
NO_CLIP,//無裁剪,直接放大 CLIP_CIRCLE,//圓形裁剪
}@Overrideprotected void onDraw(Canvas 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;
//如果版本過低,無裁剪,直接放大 invalidate();

} //可擴充更多模式....
}
}
}複製程式碼
2.落點在圖片邊界區域處理:
矩形區域校驗.png
@Overridepublic 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 h) {
return Math.abs(dstX - srcX) <
w / 2 &
&
Math.abs(dstY - srcY) <
h / 2;

}複製程式碼

五、上傳github併成庫

0.變成庫!!,變成庫!!,變成庫!!
變成庫.png

1.上傳github
上傳github.png

2.釋出:
1.png
2.png

3.檢視:jitpack.io/
see1.png
4.測試使用:
使用.png

ok,本篇完結


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1–github 2018-11-17 Android自定義控制元件之區域性圖片放大鏡–BiggerView
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1—-本文由張風捷特烈原創,轉載請註明
2—-歡迎廣大程式設計愛好者共同交流
3—-個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4—-看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

來源:https://juejin.im/post/5bf226555188250ab76961ab#comment

相關文章