Android自定義控制元件之區域性圖片放大鏡--BiggerView
零、前言:
本文的知識點一覽
1.自定義控制元件及
自定義屬性
的寫法,你也將對onMesure
有更深的認識
2.關於bitmap的簡單處
理,及canvas區域裁剪
3.本文會實現兩個自定義控制元件:FitImageView(圖片自適應)
和BiggerView(放大鏡)
,前者為後者作為鋪墊。
4.最後會介紹如何從guihub生成自己的依賴庫
,這樣一個完整的自定義控制元件庫便ok了。
5.本專案原始碼見文尾捷文規範
第一條
實現效果一覽:
1.放大鏡效果1:
2.放大鏡效果2:(使用了clipOutPath需要API26)
3.該控制元件已做成類庫(歡迎star),使用:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.toly1994328:BiggerView:v1.01'
}
複製程式碼
一、寬高等比例自適應的控制元件:FitImageView
一開始想做放大鏡效果,沒多想就繼承ImageView了,後來越做越困難,bitmap的裁剪模式會影響檢視中顯示圖片的大小。
而View自己的的大小不變,會導致圖片顯示寬高捕捉困難,和圖片左上角捕捉困難。
這就會導致繪製放大圖片時的定位適配困難,那麼多裁剪模式,想想都崩潰。
於是我想到,自己定義影象顯示的view算了,需求是寬高按比例適應,並且View的尺寸即圖片的尺寸,
將藍色作為背景,結果如下,你應該明白是什麼意思了吧,就是既想要圖片不變形,又想不要超出的背景區域:
1.自定義屬性:
<!--圖片放大鏡-->
<declare-styleable name="FitImageView">
<!--圖片資源-->
<attr name="z_fit_src" format="reference"/>
</declare-styleable>
複製程式碼
2.自定義控制元件初始程式碼
/**
* 作者:張風捷特烈<br/>
* 時間:2018/11/19 0019:0:14<br/>
* 郵箱: [email protected]<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.測量及擺放:(這是核心處理)
@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();//圖片寬高比
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裡,因為要先測量後才能知道縮放比
@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;//原圖已無用將原圖置空
}
/**
* 建立一個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);
}
@Override
protected 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);
}
}
}
複製程式碼
二、初級階段
點選的時候生成一個圓球,並隨著手指移動跟隨移動,鬆開手時消失,如圖:
這個小球就是將來展示區域性放大效果的地方
1.新增成員變數:
private int mBvRadius = dp(30);//半徑
private Paint mPaint;//主畫筆
private float mCurX;//當前觸點X
private float mCurY;//當前觸點Y
private boolean isDown;//是否觸控
複製程式碼
2.觸點的處理
@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;
}
複製程式碼
3.繪製
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isDown) {
canvas.drawCircle(mCurX, mCurY, mBvRadius, mPaint);
}
}
複製程式碼
三、中級階段:(放大圖片的處理)
1.在onLayout時建立一個rate倍大小的Bitmap
@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);
}
複製程式碼
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及以上
一開始觸點是在圓的中心,這裡往上調了一下(理由很簡單,手指太大,把要看的部位遮住了...)
但這有個問題,就是最上面的部分再往上就無法顯示了,使用做了如下的優化:
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,//圓形裁剪
}
@Override
protected 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.落點在圖片邊界區域處理:
@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 h) {
return Math.abs(dstX - srcX) < w / 2 && Math.abs(dstY - srcY) < h / 2;
}
複製程式碼
五、上傳github併成庫
0.變成庫!!,變成庫!!,變成庫!!
1.上傳github
2.釋出:
3.檢視:jitpack.io/
4.測試使用:
ok,本篇完結
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-17 | Android自定義控制元件之區域性圖片放大鏡--BiggerView |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的掘金 | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援