实用控件分享:自定义逼真相机光圈View
最近手机界开始流行双摄像头,大光圈功能也应用而生。所谓大光圈功能就是能够对照片进行后期重新对焦,其实现的原理主要是对拍照期间获取的深度图片与对焦无穷远的图像通过算法来实现重新对焦的效果。
在某双摄手机的大光圈操作界面有个光圈的操作图标,能够模拟光圈调节时的真实效果,感觉还不错,于是想着实现该效果。现在把我的实现方法贡献给大家,万一你们公司也要做双摄手机呢?( ̄┰ ̄*)
首先,百度一下光圈图片,观察观察,就可以发现其关键在于计算不同的光圈值时各个光圈叶片的位置。为了计算简便,我以六个直边叶片的光圈效果为例来实现(其他形式,比如七个叶片,也就是位置计算稍微没那么方便;而一些圆弧的叶片,只要满足叶片两边的圆弧半径是一样的就行。为什么要圆弧半径一样呢?仔细观察就可以发现,相邻两叶片之间要相互滑动,而且要保持一样的契合距离,根据我曾今小学几何科打满分的经验可以判断出,等径的圆弧是不错滴,其他高级曲线能不能实现该效果,请问数学家( ̄┰ ̄*)!其他部分原理都是一样的)。
制作效果图:

先说明一下本自定义view的主要内容:
- 本效果的实现就是在光圈内六边形六个角上分别绘制六个光圈叶片
- 根据不同的光圈值计算出内六边形的大小,从而计算每个六边形的顶点的位置
- 设计叶片。也可以让美工MM提供,本方案是自己用代码画的。注意预留叶片之间的间隔距离以及每个叶片的角度为60°
- 定义颜色、间隔等自定义属性
- 上下滑动可以调节光圈大小
- 提供光圈值变动的监听接口
代码
可以在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的更多相关文章
- WPF 4 DataGrid 控件(自定义样式篇)
原文:WPF 4 DataGrid 控件(自定义样式篇) 在<WPF 4 DataGrid 控件(基本功能篇)>中我们已经学习了DataGrid 的基本功能及使用方法.本篇将继续 ...
- WPF Calendar 日历控件 样式自定义
原文:WPF Calendar 日历控件 样式自定义 粗略的在代码上做了些注释 blend 生成出来的模版 有的时候 会生成 跟 vs ui界面不兼容的代码 会导致可视化设计界面 报错崩溃掉 但是确不 ...
- Vue input 控件: 通过自定义指令(directive)使用正则表达式限制input控件的输入
前言: 网站中的input输入框使用非常广泛,因业务场景不同需要对输入框做合法性校验或限制输入,比如电话号码.邮件.区号.身份证号等.input框的不合法内容主要有两种方式处理:1.用户输入内容后,通 ...
- 从零开始学ios开发(四):IOS控件(1),Image View、Text Field、Keyboard
长话短说,谢谢大家的关注,这篇写了好长时间,下面继续学习ios.我将用2到3篇的篇幅来学习iphone上的一些常用控件,包括Image View.Text Field.Keyboard.Slider等 ...
- 【转】C# 控件的自定义拖动、改变大小方法
在用VS的窗体设计器时,我们可以发现控件都是可以拖动的,并且还可以调整大小.怎么在自己的程序中可以使用上述功能呢? 下面的方法值得借鉴! using System; using System.Wind ...
- WPF自定义控件与样式(10)-进度控件ProcessBar自定义样
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Pro ...
- Android学习笔记(九)——布局和控件的自定义
//此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! View是 Android中一种最基本的 UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件 ...
- Flex 文本控件实现自定义复制粘贴
由于添加了自定义右键菜单,导致Textinput控件默认的右键复制粘贴功能被屏蔽了.最后通过JS脚本实现这个功能,参考代码如下 <?xml version="1.0" enc ...
- UIButton图片文字控件位置自定义(图片居右文字居左、图片居中文字居中、图片居左文字消失等)
在开发中经常会碰到需要对按钮中的图片文字位置做调整的需求.第一种方式是通过设置按钮中图片文字的偏移量.通过方法setTitleEdgeInsets和setImageEdgeInsets实现 代码如下: ...
随机推荐
- JSON入门指南--客户端处理JSON
在传统的Web开发过程中,前端工程师或者后台工程师会在页面上写后台的相关代码,比如在ASP.NET MVC4里面写如下代码: @Html.TextBoxFor(m => m.UserName, ...
- [OpenCV] Convolutional Neural Network
Ref: 从LeNet-5看卷积神经网络CNNs 关于这篇论文的一些博文的QAC: 1. 基本原理 MLP(Multilayer Perceptron,多层感知器)是一种前向神经网络(如下图所示),相 ...
- 使用MAT(Memory Analyzer Tool)工具分析dump文件--转
原文地址:http://gao-xianglong.iteye.com/blog/2173140?utm_source=tuicool&utm_medium=referral 前言 生产环境中 ...
- java 中多线程之间的通讯之生产者和消费者 (多个线程之间的通讯)
在真实开发 中关于多线程的通讯的问题用到下边的例子是比较多的 不同的地方时if 和while 的区别 如果只是两个线程之间的通讯,使用if是没有问题的. 但是在多个线程之间就会有问题 /* * 这个例 ...
- 一台电脑上的git同时使用两个github账户
需求: 公司有github账号,自己有github账号,想在git上同时使用,两者互不干扰. 思路: 管理两个SHH key. 解决方案: 一.生成两个SSH key 为了举例方便,这里使用“one” ...
- 解决ArcGIS中因SDE或数据库配置问题而导致服务宕掉的一种思路
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 最近连续有两个项目现场出现了AGS服务荡掉的问题,一个是通州 ...
- 利用SimpleExpandableListAdapter为ExpandableListActivity提供数据
首先MainActivity继承自ExpandableListActivity,其中的声明如下: setContentView(R.layout.expandmain); //定义一个:List,该L ...
- 自己动手丰衣足食之 jQuery 数量加减插件
引言 做一个手机端的订单相关项目中,其中下订单时需要用到数量加减的控件,可以设置默认值,也可以设置最大值和最小值.使用jQuery这么长时间了,平时很少去编写属于自己的插件,现在编写的时候对立面的一些 ...
- php 简单权限管理实现
权限管理介绍.与简单实现思想 权限管理 此函数为模块访问权限管理 实现思路为:根据传进来的权限值,进入不同的权限获取区间,然后根据模块名字判断是否有这个模块的访问权限 使用说明:在每一个模块开始之前调 ...
- ASP.NET MVC中获取URL地址参数的两种写法
一.url地址传参的第一种写法 1.通过mvc中默认的url地址书写格式:控制器/方法名/参数 2.实例:http://localhost:39270/RequestDemo/Index/88,默认参 ...