最近手机界开始流行双摄像头,大光圈功能也应用而生。所谓大光圈功能就是能够对照片进行后期重新对焦,其实现的原理主要是对拍照期间获取的深度图片与对焦无穷远的图像通过算法来实现重新对焦的效果。

在某双摄手机的大光圈操作界面有个光圈的操作图标,能够模拟光圈调节时的真实效果,感觉还不错,于是想着实现该效果。现在把我的实现方法贡献给大家,万一你们公司也要做双摄手机呢?( ̄┰ ̄*)

首先,百度一下光圈图片,观察观察,就可以发现其关键在于计算不同的光圈值时各个光圈叶片的位置。为了计算简便,我以六个直边叶片的光圈效果为例来实现(其他形式,比如七个叶片,也就是位置计算稍微没那么方便;而一些圆弧的叶片,只要满足叶片两边的圆弧半径是一样的就行。为什么要圆弧半径一样呢?仔细观察就可以发现,相邻两叶片之间要相互滑动,而且要保持一样的契合距离,根据我曾今小学几何科打满分的经验可以判断出,等径的圆弧是不错滴,其他高级曲线能不能实现该效果,请问数学家( ̄┰ ̄*)!其他部分原理都是一样的)。

制作效果图

先说明一下本自定义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>

实用控件分享:自定义逼真相机光圈View的更多相关文章

  1. WPF 4 DataGrid 控件(自定义样式篇)

    原文:WPF 4 DataGrid 控件(自定义样式篇)      在<WPF 4 DataGrid 控件(基本功能篇)>中我们已经学习了DataGrid 的基本功能及使用方法.本篇将继续 ...

  2. WPF Calendar 日历控件 样式自定义

    原文:WPF Calendar 日历控件 样式自定义 粗略的在代码上做了些注释 blend 生成出来的模版 有的时候 会生成 跟 vs ui界面不兼容的代码 会导致可视化设计界面 报错崩溃掉 但是确不 ...

  3. Vue input 控件: 通过自定义指令(directive)使用正则表达式限制input控件的输入

    前言: 网站中的input输入框使用非常广泛,因业务场景不同需要对输入框做合法性校验或限制输入,比如电话号码.邮件.区号.身份证号等.input框的不合法内容主要有两种方式处理:1.用户输入内容后,通 ...

  4. 从零开始学ios开发(四):IOS控件(1),Image View、Text Field、Keyboard

    长话短说,谢谢大家的关注,这篇写了好长时间,下面继续学习ios.我将用2到3篇的篇幅来学习iphone上的一些常用控件,包括Image View.Text Field.Keyboard.Slider等 ...

  5. 【转】C# 控件的自定义拖动、改变大小方法

    在用VS的窗体设计器时,我们可以发现控件都是可以拖动的,并且还可以调整大小.怎么在自己的程序中可以使用上述功能呢? 下面的方法值得借鉴! using System; using System.Wind ...

  6. WPF自定义控件与样式(10)-进度控件ProcessBar自定义样

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Pro ...

  7. Android学习笔记(九)——布局和控件的自定义

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! View是 Android中一种最基本的 UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件 ...

  8. Flex 文本控件实现自定义复制粘贴

    由于添加了自定义右键菜单,导致Textinput控件默认的右键复制粘贴功能被屏蔽了.最后通过JS脚本实现这个功能,参考代码如下 <?xml version="1.0" enc ...

  9. UIButton图片文字控件位置自定义(图片居右文字居左、图片居中文字居中、图片居左文字消失等)

    在开发中经常会碰到需要对按钮中的图片文字位置做调整的需求.第一种方式是通过设置按钮中图片文字的偏移量.通过方法setTitleEdgeInsets和setImageEdgeInsets实现 代码如下: ...

随机推荐

  1. PS批处理的使用

    一. 前言 做开发的时候,最多的时候就是图片的使用了.有时候图片的处理都按照同样的步骤,比如说统一将图片的大小调整为固定大小,或者统一在所有的图片的的某个位置上加入文字或者小图片等等,这时候PS的批处 ...

  2. Delete Volume 操作 - 每天5分钟玩转 OpenStack(57)

    今天讨论 cinder 如何删除 volume . 状态为 Available 的 volume 才能够被 delete.如果 volume 当前已经 attach 到 instance,需要先 de ...

  3. Web APi之HttpClient注意事项以及建议(四)

    前言 之前对于用SelfHost来手动实现Web API的宿主模式,似乎不是太深入,所以本篇文章我们一起来讨论关于利用HttpClient来访问Web API上的资源来进行探讨以及注意相关事项,希望此 ...

  4. Android图片选择器

    1.概述 应公司项目需求,要做一个图片选择器,网上搜索了一些源码,我在别人的基础上进行了修改,另外页面也进行了重整,我的是先加载图片文件夹列表,然后再进入选择图片.            参考博客地址 ...

  5. iOS开发之遍历Model类的属性并完善使用Runtime给Model类赋值

    在上篇博客<iOS开发之使用Runtime给Model类赋值>中介绍了如何使用运行时在实体类的基类中添加给实体类的属性赋值的方法,这个方法的前提是字典的Key必须和实体类的Property ...

  6. 关于Simple_html_dom的小应用

    今天一同学给我推荐了本书,说是刚出不久,内容还不错,是心灵鸡汤类的书,于是按捺不住就像在网上下一本,可是木有资源肿么办.只有在线看的,作为一个准码农,所以甭废话了,咱得用代码解决问题对吧…… 1.工欲 ...

  7. c# 实现简单的socket通信

    服务端 using System.Net.Sockets; using System.Net; using System.Threading; namespace SocketServer { cla ...

  8. 【iOS】NSNumberFormatter

    介绍 NSNumberFormatter 应该可以满足你对数据形式的一般需求,值得了解一下. NSNumber *num1 = [NSNumber numberWithDouble:1234567.8 ...

  9. matlab基础教程——根据Andrew Ng的machine learning整理

    matlab基础教程--根据Andrew Ng的machine learning整理 基本运算 算数运算 逻辑运算 格式化输出 小数位全局修改 向量和矩阵运算 矩阵操作 申明一个矩阵或向量 快速建立一 ...

  10. 附录D 安装ZooKeeper

    D.1    安装ZooKeeper D.1.1   下载ZooKeeper ZooKeeper是Apache基金会的一个开源.分布式应用程序协调服务,是Google的Chubby一个开源的实现.它是 ...