Android實(shí)用控件自定義逼真相機(jī)光圈View
最近手機(jī)界開(kāi)始流行雙攝像頭,大光圈功能也應(yīng)用而生。所謂大光圈功能就是能夠?qū)φ掌M(jìn)行后期重新對(duì)焦,其實(shí)現(xiàn)的原理主要是對(duì)拍照期間獲取的深度圖片與對(duì)焦無(wú)窮遠(yuǎn)的圖像通過(guò)算法來(lái)實(shí)現(xiàn)重新對(duì)焦的效果。
在某雙攝手機(jī)的大光圈操作界面有個(gè)光圈的操作圖標(biāo),能夠模擬光圈調(diào)節(jié)時(shí)的真實(shí)效果,感覺(jué)還不錯(cuò),于是想著實(shí)現(xiàn)該效果?,F(xiàn)在把我的實(shí)現(xiàn)方法貢獻(xiàn)給大家,萬(wàn)一你們公司也要做雙攝手機(jī)呢?( ̄┰ ̄*)
首先,百度一下光圈圖片,觀察觀察,就可以發(fā)現(xiàn)其關(guān)鍵在于計(jì)算不同的光圈值時(shí)各個(gè)光圈葉片的位置。為了計(jì)算簡(jiǎn)便,我以六個(gè)直邊葉片的光圈效果為例來(lái)實(shí)現(xiàn)(其他形式,比如七個(gè)葉片,也就是位置計(jì)算稍微沒(méi)那么方便;而一些圓弧的葉片,只要滿(mǎn)足葉片兩邊的圓弧半徑是一樣的就行。為什么要圓弧半徑一樣呢?仔細(xì)觀察就可以發(fā)現(xiàn),相鄰兩葉片之間要相互滑動(dòng),而且要保持一樣的契合距離,根據(jù)我曾今小學(xué)幾何科打滿(mǎn)分的經(jīng)驗(yàn)可以判斷出,等徑的圓弧是不錯(cuò)滴,其他高級(jí)曲線能不能實(shí)現(xiàn)該效果,請(qǐng)問(wèn)數(shù)學(xué)家( ̄┰ ̄*)!其他部分原理都是一樣的)。
制作效果圖:

先說(shuō)明一下本自定義view的主要內(nèi)容:
1.本效果的實(shí)現(xiàn)就是在光圈內(nèi)六邊形六個(gè)角上分別繪制六個(gè)光圈葉片
2.根據(jù)不同的光圈值計(jì)算出內(nèi)六邊形的大小,從而計(jì)算每個(gè)六邊形的頂點(diǎn)的位置
3.設(shè)計(jì)葉片。也可以讓美工MM提供,本方案是自己用代碼畫(huà)的。注意預(yù)留葉片之間的間隔距離以及每個(gè)葉片的角度為60°
4.定義顏色、間隔等自定義屬性
5.上下滑動(dòng)可以調(diào)節(jié)光圈大小
6.提供光圈值變動(dòng)的監(jiān)聽(tīng)接口
代碼
可以在GitHub上下載:https://github.com/willhua/CameraAperture.git
package com.example.cameraaperture;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* 上下滑動(dòng)可以調(diào)節(jié)光圈大小;
* 調(diào)用setApertureChangedListener設(shè)置光圈值變動(dòng)監(jiān)聽(tīng)接口;
* 繪制的光圈最大直徑將填滿(mǎn)整個(gè)view
* @author willhua http://www.cnblogs.com/willhua/
*
*/
public class ApertureView extends View {
public interface ApertureChanged {
public void onApertureChanged(float newapert);
}
private static final float ROTATE_ANGLE = 30;
private static final String TAG = "ApertureView";
private static final float COS_30 = 0.866025f;
private static final int WIDTH = 100; // 當(dāng)設(shè)置為wrap_content時(shí)測(cè)量大小
private static final int HEIGHT = 100;
private int mCircleRadius;
private int mBladeColor;
private int mBackgroundColor;
private int mSpace;
private float mMaxApert = 1;
private float mMinApert = 0.2f;
private float mCurrentApert = 0.5f;
//利用PointF而不是Point可以減少計(jì)算誤差,以免葉片之間間隔由于計(jì)算誤差而不均衡
private PointF[] mPoints = new PointF[6];
private Bitmap mBlade;
private Paint mPaint;
private Path mPath;
private ApertureChanged mApertureChanged;
private float mPrevX;
private float mPrevY;
public ApertureView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
//讀取自定義布局屬性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ApertureView);
mSpace = (int)array.getDimension(R.styleable.ApertureView_blade_space, 5);
mBladeColor = array.getColor(R.styleable.ApertureView_blade_color, 0xFF000000);
mBackgroundColor = array.getColor(R.styleable.ApertureView_background_color, 0xFFFFFFFF);
array.recycle();
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
mPaint.setAntiAlias(true);
for (int i = 0; i < 6; i++) {
mPoints[i] = new PointF();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int paddX = getPaddingLeft() + getPaddingRight();
int paddY = getPaddingTop() + getPaddingBottom();
//光圈的大小要考慮減去view的padding值
mCircleRadius = widthSpecSize - paddX < heightSpecSize - paddY ? (widthSpecSize - paddX) / 2
: (heightSpecSize - paddY) / 2;
//對(duì)布局參數(shù)為wrap_content時(shí)的處理
if (widthSpecMode == MeasureSpec.AT_MOST
&& heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(WIDTH, HEIGHT);
mCircleRadius = (WIDTH - paddX) / 2;
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(WIDTH, heightSpecSize);
mCircleRadius = WIDTH - paddX < heightSpecSize - paddY ? (WIDTH - paddX) / 2
: (heightSpecSize - paddY) / 2;
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, HEIGHT);
mCircleRadius = widthSpecSize - paddX < HEIGHT - paddY ? (widthSpecSize - paddX) / 2
: (HEIGHT - paddY) / 2;
}
if (mCircleRadius < 1) {
mCircleRadius = 1;
}
//measure之后才能知道所需要繪制的光圈大小
mPath = new Path();
mPath.addCircle(0, 0, mCircleRadius, Path.Direction.CW);
createBlade();
}
@Override
public void onDraw(Canvas canvas) {
canvas.save();
calculatePoints();
//先把canbvas平移到view的中間
canvas.translate(getWidth() / 2, getHeight() / 2);
//讓光圈的葉片整體旋轉(zhuǎn),更加貼合實(shí)際
canvas.rotate(ROTATE_ANGLE * (mCurrentApert - mMinApert) / (mMaxApert - mMinApert));
canvas.clipPath(mPath);
canvas.drawColor(mBackgroundColor);
for (int i = 0; i < 6; i++) {
canvas.save();
canvas.translate(mPoints[i].x, mPoints[i].y);
canvas.rotate(-i * 60);
canvas.drawBitmap(mBlade, 0, 0, mPaint);
canvas.restore();
}
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() > 1) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = event.getX();
mPrevY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float diffx = Math.abs((event.getX() - mPrevX));
float diffy = Math.abs((event.getY() - mPrevY));
if (diffy > diffx) { // 豎直方向的滑動(dòng)
float diff = (float) Math.sqrt(diffx * diffx + diffy * diffy)
/ mCircleRadius * mMaxApert;
if (event.getY() > mPrevY) { //判斷方向
setCurrentApert(mCurrentApert - diff);
} else {
setCurrentApert(mCurrentApert + diff);
}
mPrevX = event.getX();
mPrevY = event.getY();
}
break;
default:
break;
}
return true;
}
private void calculatePoints() {
if (mCircleRadius - mSpace <= 0) {
Log.e(TAG, "the size of view is too small and Space is too large");
return;
}
//mCircleRadius - mSpace可以保證內(nèi)嵌六邊形在光圈內(nèi)
float curRadius = mCurrentApert / mMaxApert * (mCircleRadius - mSpace);
//利用對(duì)稱(chēng)關(guān)系,減少計(jì)算
mPoints[0].x = curRadius / 2;
mPoints[0].y = -curRadius * COS_30;
mPoints[1].x = -mPoints[0].x;
mPoints[1].y = mPoints[0].y;
mPoints[2].x = -curRadius;
mPoints[2].y = 0;
mPoints[3].x = mPoints[1].x;
mPoints[3].y = -mPoints[1].y;
mPoints[4].x = -mPoints[3].x;
mPoints[4].y = mPoints[3].y;
mPoints[5].x = curRadius;
mPoints[5].y = 0;
}
//創(chuàng)建光圈葉片,讓美工MM提供更好
private void createBlade() {
mBlade = Bitmap.createBitmap(mCircleRadius,
(int) (mCircleRadius * 2 * COS_30), Config.ARGB_8888);
Path path = new Path();
Canvas canvas = new Canvas(mBlade);
path.moveTo(mSpace / 2 / COS_30, mSpace);
path.lineTo(mBlade.getWidth(), mBlade.getHeight());
path.lineTo(mBlade.getWidth(), mSpace);
path.close();
canvas.clipPath(path);
canvas.drawColor(mBladeColor);
}
/**
* 設(shè)置光圈片的顏色
* @param bladeColor
*/
public void setBladeColor(int bladeColor) {
mBladeColor = bladeColor;
}
/**
* 設(shè)置光圈背景色
*/
public void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
/**
* 設(shè)置光圈片之間的間隔
* @param space
*/
public void setSpace(int space) {
mSpace = space;
}
/**
* 設(shè)置光圈最大值
* @param maxApert
*/
public void setMaxApert(float maxApert) {
mMaxApert = maxApert;
}
/**
* 設(shè)置光圈最小值
* @param mMinApert
*/
public void setMinApert(float mMinApert) {
this.mMinApert = mMinApert;
}
public float getCurrentApert() {
return mCurrentApert;
}
public void setCurrentApert(float currentApert) {
if (currentApert > mMaxApert) {
currentApert = mMaxApert;
}
if (currentApert < mMinApert) {
currentApert = mMinApert;
}
if (mCurrentApert == currentApert) {
return;
}
mCurrentApert = currentApert;
invalidate();
if (mApertureChanged != null) {
mApertureChanged.onApertureChanged(currentApert);
}
}
/**
* 設(shè)置光圈值變動(dòng)的監(jiān)聽(tīng)
* @param listener
*/
public void setApertureChangedListener(ApertureChanged listener) {
mApertureChanged = listener;
}
}
自定義屬性的xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ApertureView">
<attr name="blade_color" format="color" />
<attr name="background_color" format="color" />
<attr name="blade_space" format="dimension" />
</declare-styleable>
</resources>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 用 camera2 API 自定義相機(jī)
- Android中關(guān)于自定義相機(jī)預(yù)覽界面拉伸問(wèn)題
- Android自定義相機(jī)實(shí)現(xiàn)定時(shí)拍照功能
- Android自定義組件獲取本地圖片和相機(jī)拍照?qǐng)D片
- Android自定義相機(jī)實(shí)現(xiàn)自動(dòng)對(duì)焦和手動(dòng)對(duì)焦
- Android自定義相機(jī)界面的實(shí)現(xiàn)代碼
- Android自定義照相機(jī)Camera出現(xiàn)黑屏的解決方法
- Android自定義照相機(jī)詳解
- Android自定義照相機(jī)倒計(jì)時(shí)拍照
- Android 自定義相機(jī)及分析源碼
相關(guān)文章
Android實(shí)現(xiàn)MVVM架構(gòu)數(shù)據(jù)刷新詳解流程
MVVM架構(gòu)模式,即Model-View-ViewModel三個(gè)層級(jí),MVVM模式出來(lái)的時(shí)間已經(jīng)很長(zhǎng)了,網(wǎng)上關(guān)于MVVM模式的解析也有很多,我這里只說(shuō)一下我自己的理解,基本上是和MVP模式相比較的一個(gè)差異2021-10-10
Android入門(mén)之使用RecyclerView完美實(shí)現(xiàn)瀑布流界面詳解
網(wǎng)上充滿(mǎn)著不完善的基于RecyclerView的瀑布流實(shí)現(xiàn),要么根本是錯(cuò)的、要么就是只知其一不知其二。本文就來(lái)用RecyclerView完美實(shí)現(xiàn)瀑布流界面,希望大家有所幫助2023-02-02
Android6.0指紋識(shí)別開(kāi)發(fā)案例
這篇文章主要為大家分享了Android6.0指紋識(shí)別開(kāi)發(fā)案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android Studio 4.0 新功能中的Live Layout Inspector詳解
這篇文章主要介紹了Android Studio 4.0 新功能中的Live Layout Inspector,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android中多行文本末尾添加圖片排版問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于Android中多行文本末尾添加圖片排版問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
使用Android Studio實(shí)現(xiàn)為系統(tǒng)級(jí)的app簽名
這篇文章主要介紹了使用Android Studio實(shí)現(xiàn)為系統(tǒng)級(jí)的app簽名,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android SeekBar 自定義thumb旋轉(zhuǎn)動(dòng)畫(huà)效果
某些音樂(lè)播放或者視頻播放的界面上,資源還在加載時(shí),進(jìn)度條的原點(diǎn)(thumb)會(huì)顯示一個(gè)轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動(dòng)畫(huà)效果,需要的朋友可以參考下2021-11-11
android LabelView實(shí)現(xiàn)標(biāo)簽云效果
這篇文章主要為大家詳細(xì)介紹了android LabelView實(shí)現(xiàn)標(biāo)簽云效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05

