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

先說明一下本自定義view的主要內(nèi)容:
1.本效果的實現(xiàn)就是在光圈內(nèi)六邊形六個角上分別繪制六個光圈葉片
2.根據(jù)不同的光圈值計算出內(nèi)六邊形的大小,從而計算每個六邊形的頂點的位置
3.設(shè)計葉片。也可以讓美工MM提供,本方案是自己用代碼畫的。注意預(yù)留葉片之間的間隔距離以及每個葉片的角度為60°
4.定義顏色、間隔等自定義屬性
5.上下滑動可以調(diào)節(jié)光圈大小
6.提供光圈值變動的監(jiān)聽接口
代碼
可以在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;
/**
* 上下滑動可以調(diào)節(jié)光圈大??;
* 調(diào)用setApertureChangedListener設(shè)置光圈值變動監(jiān)聽接口;
* 繪制的光圈最大直徑將填滿整個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時測量大小
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可以減少計算誤差,以免葉片之間間隔由于計算誤差而不均衡
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;
//對布局參數(shù)為wrap_content時的處理
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),更加貼合實際
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) { // 豎直方向的滑動
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);
//利用對稱關(guān)系,減少計算
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è)置光圈值變動的監(jiān)聽
* @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>
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實現(xiàn)雅虎新聞?wù)虞d視差動畫效果
這篇文章主要介紹了Android實現(xiàn)雅虎新聞?wù)虞d視差動畫效果,通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
Android編程實現(xiàn)ListView內(nèi)容無限循環(huán)顯示的方法
這篇文章主要介紹了Android編程實現(xiàn)ListView內(nèi)容無限循環(huán)顯示的方法,通過繼承Adapter類實現(xiàn)ListView中的數(shù)據(jù)無限循環(huán)顯示功能,需要的朋友可以參考下2017-06-06
android 使用uinput模擬輸入設(shè)備的方法
這篇文章主要介紹了android 使用uinput模擬輸入設(shè)備的方法,有需要的朋友可以參考一下2014-01-01
Android中ACTION_CANCEL的觸發(fā)機(jī)制與滑出子view的情況
這篇文章主要介紹了Android中ACTION_CANCEL的觸發(fā)機(jī)制與滑出子view的情況,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09
使用ViewPager實現(xiàn)高仿launcher左右拖動效果
今天用ViewPager這個類實現(xiàn)了同樣的左右拖動效果,這樣代碼更少,但是效果是一樣的,ViewPager是實現(xiàn)左右兩個屏幕平滑地切換的一個類,它是Google提供的,有需要的朋友可以了解下2013-01-01
Android自定義View繪制四位數(shù)隨機(jī)碼
這篇文章主要介紹了Android自定義View繪制四位數(shù)隨機(jī)碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10

