最近手机界开始流行双摄像头,大光圈功能也应用而生。所谓大光圈功能就是能够对照片进行后期重新对焦,其实现的原理主要是对拍照期间获取的深度图片与对焦无穷远的图像通过算法来实现重新对焦的效果。
在某双摄手机的大光圈操作界面有个光圈的操作图标,能够模拟光圈调节时的真实效果,感觉还不错,于是想着实现该效果。现在把我的实现方法贡献给大家,万一你们公司也要做双摄手机呢?( ̄┰ ̄*)
首先,百度一下光圈图片,观察观察,就可以发现其关键在于计算不同的光圈值时各个光圈叶片的位置。为了计算简便,我以六个直边叶片的光圈效果为例来实现(其他形式,比如七个叶片,也就是位置计算稍微没那么方便;而一些圆弧的叶片,只要满足叶片两边的圆弧半径是一样的就行。为什么要圆弧半径一样呢?仔细观察就可以发现,相邻两叶片之间要相互滑动,而且要保持一样的契合距离,根据我曾今小学几何科打满分的经验可以判断出,等径的圆弧是不错滴,其他高级曲线能不能实现该效果,请问数学家( ̄┰ ̄*)!其他部分原理都是一样的)。
制作效果图:
先说明一下本自定义view的主要内容:
1.本效果的实现就是在光圈内六边形六个角上分别绘制六个光圈叶片
2.根据不同的光圈值计算出内六边形的大小,从而计算每个六边形的顶点的位置
3.设计叶片。也可以让美工MM提供,本方案是自己用代码画的。注意预留叶片之间的间隔距离以及每个叶片的角度为60°
4.定义颜色、间隔等自定义属性
5.上下滑动可以调节光圈大小
6.提供光圈值变动的监听接口
代码
可以在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; /** * 上下滑动可以调节光圈大小; * 调用setApertureChangedListener设置光圈值变动监听接口; * 绘制的光圈最大直径将填满整个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; // 当设置为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; //对布局参数为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); //让光圈的叶片整体旋转,更加贴合实际 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可以保证内嵌六边形在光圈内 float curRadius = mCurrentApert / mMaxApert * (mCircleRadius - mSpace); //利用对称关系,减少计算 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; } //创建光圈叶片,让美工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); } /** * 设置光圈片的颜色 * @param bladeColor */ public void setBladeColor(int bladeColor) { mBladeColor = bladeColor; } /** * 设置光圈背景色 */ public void setBackgroundColor(int backgroundColor) { mBackgroundColor = backgroundColor; } /** * 设置光圈片之间的间隔 * @param space */ public void setSpace(int space) { mSpace = space; } /** * 设置光圈最大值 * @param maxApert */ public void setMaxApert(float maxApert) { mMaxApert = maxApert; } /** * 设置光圈最小值 * @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); } } /** * 设置光圈值变动的监听 * @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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍Android 用 camera2 API 自定义相机,包括了Android 用 camera2 API 自定义相机的使用技巧和注意事项,需要的朋友参考一下 前言 笔者因为项目需要自定义相机,所以了解了一下 Android 关于 camera 这块的 API。Android SDK 21(LOLLIPOP) 开始已经弃用了之前的 Camera 类,提供了 camera2 相关 API
Tabris.js控件由JavaScript API和原生平台的实现组成。本文档介绍Android平台上的自定义控件的原生实现。 为了实现自定义控件你需要本地构建。 在Cordova基础上构建 为了创建Tabris.js自定义控件,我们使用Cordova的构建系统。因此,我们创建一个与Tabris.js特定的API相关联的Cordova插件。Tabris.js自定义控件不需要接触任何Cordova
本文向大家介绍Android自定义View实现自动转圈效果,包括了Android自定义View实现自动转圈效果的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android实现自动转圈效果展示的具体代码,供大家参考,具体内容如下 在values文件夹下创建attrs.xml 写一个类继承view 在主页面布局中引入自定义view类 以上就是本文的全部内容,希望对大家的学习有所帮助,也
本文向大家介绍Android自定义控件之自定义组合控件(三),包括了Android自定义控件之自定义组合控件(三)的使用技巧和注意事项,需要的朋友参考一下 前言: 前两篇介绍了自定义控件的基础原理Android自定义控件基本原理详解(一)、Android自定义控件之自定义属性(二)。今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本。 使用自定义组合控件的好处? 我
本文向大家介绍实例讲解Android自定义控件,包括了实例讲解Android自定义控件的使用技巧和注意事项,需要的朋友参考一下 小编在此之前给大家介绍过关于Android自定义控件的用法等,需要的可以参考下: Android开发之自定义控件用法详解 详解Android自定义控件属性 可以看到QQ上的ToolBar其实就是一个自定义的view,可以看到不同的界面就是简单地修改了文字而已,在第二张与第
本文向大家介绍Android自定义控件LinearLayout实例讲解,包括了Android自定义控件LinearLayout实例讲解的使用技巧和注意事项,需要的朋友参考一下 很多时候Android常用的控件不能满足我们的需求,那么我们就需要自定义一个控件了。今天做了一个自定义控件的实例,来分享下。 首先定义一个layout实现按钮内部布局: 接下来写一个类继承LinearLayout,导入刚刚