Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> 實用控件分享:自定義逼真相機光圈View,控件相機光圈view

實用控件分享:自定義逼真相機光圈View,控件相機光圈view

編輯:關於android開發

實用控件分享:自定義逼真相機光圈View,控件相機光圈view


最近手機界開始流行雙攝像頭,大光圈功能也應用而生。所謂大光圈功能就是能夠對照片進行後期重新對焦,其實現的原理主要是對拍照期間獲取的深度圖片與對焦無窮遠的圖像通過算法來實現重新對焦的效果。

在某雙攝手機的大光圈操作界面有個光圈的操作圖標,能夠模擬光圈調節時的真實效果,感覺還不錯,於是想著實現該效果。現在把我的實現方法貢獻給大家,萬一你們公司也要做雙攝手機呢?( ̄┰ ̄*)

首先,百度一下光圈圖片,觀察觀察,就可以發現其關鍵在於計算不同的光圈值時各個光圈葉片的位置。為了計算簡便,我以六個直邊葉片的光圈效果為例來實現(其他形式,比如七個葉片,也就是位置計算稍微沒那麼方便;而一些圓弧的葉片,只要滿足葉片兩邊的圓弧半徑是一樣的就行。為什麼要圓弧半徑一樣呢?仔細觀察就可以發現,相鄰兩葉片之間要相互滑動,而且要保持一樣的契合距離,根據我曾今小學幾何科打滿分的經驗可以判斷出,等徑的圓弧是不錯滴,其他高級曲線能不能實現該效果,請問數學家( ̄┰ ̄*)!其他部分原理都是一樣的)。 

制作效果圖

 

先說明一下本自定義view的主要內容:

代碼

可以在GitHub上下載:https://github.com/willhua/CameraAperture.git

  1 package com.example.cameraaperture;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.graphics.Bitmap;
  6 import android.graphics.Bitmap.Config;
  7 import android.graphics.Canvas;
  8 import android.graphics.Paint;
  9 import android.graphics.Path;
 10 import android.graphics.PointF;
 11 import android.util.AttributeSet;
 12 import android.util.Log;
 13 import android.view.MotionEvent;
 14 import android.view.View;
 15 
 16 /**
 17  * 上下滑動可以調節光圈大小;
 18  * 調用setApertureChangedListener設置光圈值變動監聽接口;
 19  * 繪制的光圈最大直徑將填滿整個view
 20  * @author willhua http://www.cnblogs.com/willhua/
 21  * 
 22  */
 23 public class ApertureView extends View {
 24 
 25     public interface ApertureChanged {
 26         public void onApertureChanged(float newapert);
 27     }
 28 
 29     private static final float ROTATE_ANGLE = 30;
 30     private static final String TAG = "ApertureView";
 31     private static final float COS_30 = 0.866025f;
 32     private static final int WIDTH = 100; // 當設置為wrap_content時測量大小
 33     private static final int HEIGHT = 100;
 34     private int mCircleRadius;
 35     private int mBladeColor;
 36     private int mBackgroundColor;
 37     private int mSpace;
 38     private float mMaxApert = 1;
 39     private float mMinApert = 0.2f;
 40     private float mCurrentApert = 0.5f;
 41 
 42     //利用PointF而不是Point可以減少計算誤差,以免葉片之間間隔由於計算誤差而不均衡
 43     private PointF[] mPoints = new PointF[6]; 
 44     private Bitmap mBlade;
 45     private Paint mPaint;
 46     private Path mPath;
 47     private ApertureChanged mApertureChanged;
 48 
 49     private float mPrevX;
 50     private float mPrevY;
 51 
 52     public ApertureView(Context context, AttributeSet attrs) {
 53         super(context, attrs);
 54         init(context, attrs);
 55     }
 56 
 57     private void init(Context context, AttributeSet attrs) {
 58         //讀取自定義布局屬性
 59         TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ApertureView);
 60         mSpace = (int)array.getDimension(R.styleable.ApertureView_blade_space, 5);
 61         mBladeColor = array.getColor(R.styleable.ApertureView_blade_color, 0xFF000000);
 62         mBackgroundColor = array.getColor(R.styleable.ApertureView_background_color, 0xFFFFFFFF);
 63         array.recycle();
 64         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
 65         mPaint.setAntiAlias(true);
 66         for (int i = 0; i < 6; i++) {
 67             mPoints[i] = new PointF();
 68         }
 69     }
 70 
 71     @Override
 72     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 73         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 74         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 75         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 76         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 77         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 78         int paddX = getPaddingLeft() + getPaddingRight();
 79         int paddY = getPaddingTop() + getPaddingBottom();
 80         //光圈的大小要考慮減去view的padding值
 81         mCircleRadius = widthSpecSize - paddX < heightSpecSize - paddY ? (widthSpecSize - paddX) / 2
 82                 : (heightSpecSize - paddY) / 2;
 83         //對布局參數為wrap_content時的處理
 84         if (widthSpecMode == MeasureSpec.AT_MOST
 85                 && heightSpecMode == MeasureSpec.AT_MOST) {
 86             setMeasuredDimension(WIDTH, HEIGHT);
 87             mCircleRadius = (WIDTH - paddX) / 2;
 88         } else if (widthSpecMode == MeasureSpec.AT_MOST) {
 89             setMeasuredDimension(WIDTH, heightSpecSize);
 90             mCircleRadius = WIDTH - paddX < heightSpecSize - paddY ? (WIDTH - paddX) / 2
 91                     : (heightSpecSize - paddY) / 2;
 92         } else if (heightSpecMode == MeasureSpec.AT_MOST) {
 93             setMeasuredDimension(widthSpecSize, HEIGHT);
 94             mCircleRadius = widthSpecSize - paddX < HEIGHT - paddY ? (widthSpecSize - paddX) / 2
 95                     : (HEIGHT - paddY) / 2;
 96         }
 97         if (mCircleRadius < 1) {
 98             mCircleRadius = 1;
 99         }
100         //measure之後才能知道所需要繪制的光圈大小
101         mPath = new Path();
102         mPath.addCircle(0, 0, mCircleRadius, Path.Direction.CW);
103         createBlade();
104     }
105 
106     @Override
107     public void onDraw(Canvas canvas) {
108         canvas.save();
109         calculatePoints();
110         //先把canbvas平移到view的中間
111         canvas.translate(getWidth() / 2, getHeight() / 2);
112         //讓光圈的葉片整體旋轉,更加貼合實際
113         canvas.rotate(ROTATE_ANGLE * (mCurrentApert - mMinApert) / (mMaxApert - mMinApert));
114         canvas.clipPath(mPath);
115         canvas.drawColor(mBackgroundColor);
116 
117         for (int i = 0; i < 6; i++) {
118             canvas.save();
119             canvas.translate(mPoints[i].x, mPoints[i].y);
120             canvas.rotate(-i * 60);
121             canvas.drawBitmap(mBlade, 0, 0, mPaint);
122             canvas.restore();
123         }
124         canvas.restore();
125     }
126 
127     @Override
128     public boolean onTouchEvent(MotionEvent event) {
129         if (event.getPointerCount() > 1) {
130             return false;
131         }
132         switch (event.getAction()) {
133         case MotionEvent.ACTION_DOWN:
134             mPrevX = event.getX();
135             mPrevY = event.getY();
136             break;
137         case MotionEvent.ACTION_MOVE:
138             float diffx = Math.abs((event.getX() - mPrevX));
139             float diffy = Math.abs((event.getY() - mPrevY));
140             if (diffy > diffx) {  // 豎直方向的滑動
141                 float diff = (float) Math.sqrt(diffx * diffx + diffy * diffy)
142                         / mCircleRadius * mMaxApert;
143                 if (event.getY() > mPrevY) {  //判斷方向
144                     setCurrentApert(mCurrentApert - diff);
145                 } else {
146                     setCurrentApert(mCurrentApert + diff);
147                 }
148                 mPrevX = event.getX();
149                 mPrevY = event.getY();
150             }
151             break;
152         default:
153             break;
154         }
155         return true;
156     }
157 
158     private void calculatePoints() {
159         if (mCircleRadius - mSpace <= 0) {
160             Log.e(TAG, "the size of view is too small and Space is too large");
161             return;
162         }
163         //mCircleRadius - mSpace可以保證內嵌六邊形在光圈內
164         float curRadius = mCurrentApert / mMaxApert * (mCircleRadius - mSpace);
165         //利用對稱關系,減少計算
166         mPoints[0].x = curRadius / 2;
167         mPoints[0].y = -curRadius * COS_30;
168         mPoints[1].x = -mPoints[0].x;
169         mPoints[1].y = mPoints[0].y;
170         mPoints[2].x = -curRadius;
171         mPoints[2].y = 0;
172         mPoints[3].x = mPoints[1].x;
173         mPoints[3].y = -mPoints[1].y;
174         mPoints[4].x = -mPoints[3].x;
175         mPoints[4].y = mPoints[3].y;
176         mPoints[5].x = curRadius;
177         mPoints[5].y = 0;
178     }
179 
180     //創建光圈葉片,讓美工MM提供更好
181     private void createBlade() {
182         mBlade = Bitmap.createBitmap(mCircleRadius,
183                 (int) (mCircleRadius * 2 * COS_30), Config.ARGB_8888);
184         Path path = new Path();
185         Canvas canvas = new Canvas(mBlade);
186         path.moveTo(mSpace / 2 / COS_30, mSpace);
187         path.lineTo(mBlade.getWidth(), mBlade.getHeight());
188         path.lineTo(mBlade.getWidth(), mSpace);
189         path.close();
190         canvas.clipPath(path);
191         canvas.drawColor(mBladeColor);
192     }
193 
194     /**
195      * 設置光圈片的顏色
196      * @param bladeColor
197      */
198     public void setBladeColor(int bladeColor) {
199         mBladeColor = bladeColor;
200     }
201 
202     /**
203      * 設置光圈背景色
204      */
205     public void setBackgroundColor(int backgroundColor) {
206         mBackgroundColor = backgroundColor;
207     }
208 
209     /**
210      * 設置光圈片之間的間隔
211      * @param space
212      */
213     public void setSpace(int space) {
214         mSpace = space;
215     }
216 
217     /**
218      * 設置光圈最大值
219      * @param maxApert
220      */
221     public void setMaxApert(float maxApert) {
222         mMaxApert = maxApert;
223     }
224 
225     /**
226      * 設置光圈最小值
227      * @param mMinApert
228      */
229     public void setMinApert(float mMinApert) {
230         this.mMinApert = mMinApert;
231     }
232 
233     public float getCurrentApert() {
234         return mCurrentApert;
235     }
236 
237     public void setCurrentApert(float currentApert) {
238         if (currentApert > mMaxApert) {
239             currentApert = mMaxApert;
240         }
241         if (currentApert < mMinApert) {
242             currentApert = mMinApert;
243         }
244         if (mCurrentApert == currentApert) {
245             return;
246         }
247         mCurrentApert = currentApert;
248         invalidate();
249         if (mApertureChanged != null) {
250             mApertureChanged.onApertureChanged(currentApert);
251         }
252     }
253 
254     /**
255      * 設置光圈值變動的監聽
256      * @param listener
257      */
258     public void setApertureChangedListener(ApertureChanged listener) {
259         mApertureChanged = listener;
260     }
261 }

自定義屬性的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>

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved