欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 仿QQ头像自定义截取功能

程序员文章站 2024-03-05 13:47:42
看了android版qq的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识。 先看看效果: 思路分析: 这个效果可以用两个view...

看了android版qq的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识。

先看看效果:

Android 仿QQ头像自定义截取功能

思路分析:

这个效果可以用两个view来完成,上层view是一个遮盖物,绘制半透明的颜色,中间挖了一个圆;下层的view用来显示图片,具备移动和缩放的功能,并且能截取某区域内的图片。

涉及到的知识点:

1.matrix,图片的移动和缩放

2.paint的setxfermode方法

3.图片放大移动后,截取一部分

编码实现:

自定义三个view:

1.下层view:clipphotoview

2.上层遮盖view:clipphotocircleview

3.布局文件:clipphotolayout,实现两层view的布局,且作为整个功能的facade

clipphotocircleview代码:

@override
protected void ondraw(canvas canvas) {
super.ondraw(canvas);
drawmask(canvas);
}
/**
* 绘制蒙版
*/
private void drawmask(canvas canvas) {
//画背景颜色
bitmap bitmap = bitmap.createbitmap(getwidth(), getheight(), bitmap.config.argb_8888);
canvas c1 = new canvas(bitmap);
c1.drawargb(150, 0, 0, 0);
paint strokepaint = new paint();
strokepaint.setantialias(true);
strokepaint.setcolor(color.white);
strokepaint.setstyle(paint.style.stroke);
strokepaint.setstrokewidth(stroke_width);
c1.drawcircle(getwidth() / 2, getheight() / 2, getradius(), strokepaint);
//画圆
bitmap circlebitmap = bitmap.createbitmap(getwidth(), getheight(), bitmap.config.argb_8888);
canvas c2 = new canvas(circlebitmap);
paint circlepaint = new paint();
circlepaint.setstyle(paint.style.fill);
circlepaint.setcolor(color.red);
circlepaint.setantialias(true);
c2.drawcircle(getwidth() / 2, getheight() / 2, getradius(), circlepaint);
//两个图层合成
paint paint = new paint();
paint.setxfermode(new porterduffxfermode(porterduff.mode.dst_out));
c1.drawbitmap(circlebitmap, 0, 0, paint);
paint.setxfermode(null);
canvas.drawbitmap(bitmap, 0, 0, null);
}

使用了setxfermode,mode为dst_out,如下图:

Android 仿QQ头像自定义截取功能

clipphotoview代码:

/**
* created by caocong on 10/9/16.
* 显示图片的view,可以托动和缩放
*/
public class clipphotoview extends imageview implements view.ontouchlistener,
scalegesturedetector.onscalegesturelistener {
private static final string tag = clipphotoview.class.getsimplename();
//最大缩放比例
private static final float max_scale = 4.0f;
//最小缩放比例
private static float min_scale = 1.0f;
//matrix array
private static final float matrix_arr[] = new float[9];
/**
* 状态
*/
private static final class mode {
// 初始状态
private static final int none = 0;
//托动
private static final int drag = 1;
//缩放
private static final int zoom = 2;
}
//当前状态
private int mmode = mode.none;
//缩放手势
private scalegesturedetector mscaledetector;
//矩阵
private matrix mmatrix = new matrix();
//托动时手指按下的点
private pointf mprevpointf = new pointf();
//截取的圆框的半径
private int mradius;
//第一次
private boolean firsttime = true;
public clipphotoview(context context) {
this(context, null);
}
public clipphotoview(context context, attributeset attrs) {
this(context, attrs, 0);
}
public clipphotoview(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
mscaledetector = new scalegesturedetector(context, this);
mradius = util.getradius(getcontext());
// 必须设置才能触发
setontouchlistener(this);
setscaletype(scaletype.matrix);
}
/**
* 初始化
*/
private void init() {
drawable drawable = getdrawable();
if (drawable == null) {
//throw new illegalargumentexception("drawable can not be null");
return;
}
initposandscale();
}
/**
* 初始化缩放比例
*/
private void initposandscale() {
if (firsttime) {
drawable drawable = getdrawable();
int width = getwidth();
int height = getheight();
//初始化
int dw = drawable.getintrinsicwidth();
int dh = drawable.getintrinsicheight();
float scalex = 1.0f;
float scaley = 1.0f;
//是否已经做过缩放处理
boolean isscaled = false;
if (width < getdiameter()) {
scalex = getdiameter() * 1.0f / width;
isscaled = true;
}
if (height < getdiameter()) {
scaley = getdiameter() * 1.0f / height;
isscaled = true;
}
float scale = math.max(scalex, scaley);
if (isscaled) {
min_scale = scale;
} else {
min_scale = math.max((getdiameter() * 1.0f) / dw, getdiameter() * 1.0f / dh) + 0.01f;
}
log.d(tag, "scale=" + scale);
mmatrix.postscale(scale, scale, getwidth() / 2, getheight() / 2);
mmatrix.posttranslate((width - dw) / 2, (height - dh) / 2);
setimagematrix(mmatrix);
firsttime = false;
}
}
@override
public boolean onscale(scalegesturedetector detector) {
float scale = getscale();
float scalefactor = detector.getscalefactor();
if ((scale >= min_scale && scalefactor > 1.0f) ||
(scale <= max_scale && scalefactor < 1.0f)) {
if (scale * scalefactor <= min_scale) {
scalefactor = min_scale / scale;
} else if (scale * scalefactor >= max_scale) {
scalefactor = max_scale / scale;
}
mmatrix.postscale(scalefactor, scalefactor, detector.getfocusx(), detector.getfocusy());
checktrans();
setimagematrix(mmatrix);
}
return true;
}
@override
public boolean onscalebegin(scalegesturedetector detector) {
mmode = mode.zoom;
return true;
}
@override
public void onscaleend(scalegesturedetector detector) {
mmode = mode.none;
}
@override
public boolean ontouch(view v, motionevent event) {
if (getdrawable() == null) {
return false;
}
mscaledetector.ontouchevent(event);
switch (event.getaction() & motionevent.action_mask) {
case motionevent.action_down:
mmode = mode.drag;
mprevpointf.set(event.getx(), event.gety());
break;
case motionevent.action_up:
mmode = mode.none;
break;
case motionevent.action_move:
if (mmode == mode.drag && event.getpointercount() == 1) {
float x = event.getx();
float y = event.gety();
float dx = event.getx() - mprevpointf.x;
float dy = event.gety() - mprevpointf.y;
rectf rectf = getmatrixrectf();
// 如果宽度小于屏幕宽度,则禁止左右移动
if (rectf.width() <= getdiameter()) {
dx = 0;
}
// 如果高度小雨屏幕高度,则禁止上下移动
if (rectf.height() <= getdiameter()) {
dy = 0;
}
mmatrix.posttranslate(dx, dy);
checktrans();
//边界判断
setimagematrix(mmatrix);
mprevpointf.set(x, y);
}
break;
}
return true;
}
/**
* 移动边界检查
*/
private void checktrans() {
rectf rect = getmatrixrectf();
float deltax = 0;
float deltay = 0;
int width = getwidth();
int height = getheight();
int horizontalpadding = (width - getdiameter()) / 2;
int verticalpadding = (height - getdiameter()) / 2;
// 如果宽或高大于屏幕,则控制范围 ; 这里的0.001是因为精度丢失会产生问题
if (rect.width() + 0.01 >= getdiameter()) {
if (rect.left > horizontalpadding) {
deltax = -rect.left + horizontalpadding;
}
if (rect.right < width - horizontalpadding) {
deltax = width - horizontalpadding - rect.right;
}
}
if (rect.height() + 0.01 >= getdiameter()) {
if (rect.top > verticalpadding) {
deltay = -rect.top + verticalpadding;
}
if (rect.bottom < height - verticalpadding) {
deltay = height - verticalpadding - rect.bottom;
}
}
mmatrix.posttranslate(deltax, deltay);
}
/**
* 得到直径
*/
public int getdiameter() {
return mradius * 2;
}
/**
* 获得缩放值
*
* @return
*/
private float getscale() {
return getmatrixvalue(matrix.mscale_x);
}
private float getmatrixvalue(int index) {
mmatrix.getvalues(matrix_arr);
return matrix_arr[index];
}
/**
* 获得matrix的rectf
*/
private rectf getmatrixrectf() {
matrix matrix = mmatrix;
rectf rect = new rectf();
drawable d = getdrawable();
if (null != d) {
rect.set(0, 0, d.getintrinsicwidth(), d.getintrinsicheight());
matrix.maprect(rect);
}
return rect;
}
@override
protected void ondraw(canvas canvas) {
super.ondraw(canvas);
init();
}
/**
* 截取图片
*
* @return
*/
bitmap clip() {
bitmap bitmap = bitmap.createbitmap(getwidth(), getheight(), bitmap.config.argb_8888);
canvas canvas = new canvas(bitmap);
draw(canvas);
int x = (getwidth() - getdiameter()) / 2;
int y = (getheight() - getdiameter()) / 2;
return bitmap.createbitmap(bitmap, x, y, getdiameter(), getdiameter());
}
}

缩放和移动使用了matrix的方法postscale()和posttranslate,要注意控制边界。

截图的代码在clip()方法中,原理:新建一个空白bitmap,和屏幕一样大的尺寸,然后将当前view绘制的内容复制到到这个bitmap中,然后截取该bitmap的一部分。

clipphotolayout代码:

public class clipphotolayout extends framelayout {
private clipphotocircleview mcircleview;
private clipphotoview mphotoview;
public clipphotolayout(context context) {
this(context, null);
}
public clipphotolayout(context context, attributeset attrs) {
this(context, attrs, 0);
}
public clipphotolayout(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
init();
}
private void init() {
mcircleview = new clipphotocircleview(getcontext());
mphotoview = new clipphotoview(getcontext());
android.view.viewgroup.layoutparams lp = new linearlayout.layoutparams(
android.view.viewgroup.layoutparams.match_parent,
android.view.viewgroup.layoutparams.match_parent);
addview(mphotoview, lp);
addview(mcircleview, lp);
}
public void setimagedrawable(drawable drawable) {
mphotoview.setimagedrawable(drawable);
}
public void setimagedrawable(int resid) {
setimagedrawable(getcontext().getdrawable(resid));
}
public bitmap clipbitmap() {
return mphotoview.clip();
}
}

测试mainactivity:

public class mainactivity extends activity {
private clipphotolayout mclipphotolayout;
private int[] pictures = {r.drawable.mingren, r.drawable.cute, r.drawable.tuxi};
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.scale);
settitle("移动和缩放");
mclipphotolayout = (clipphotolayout) findviewbyid(r.id.clip_layout);
mclipphotolayout.setimagedrawable(pictures[0]);
}
public void doclick(view view) {
bitmap bitmap = mclipphotolayout.clipbitmap();
intent intent = new intent(this, resultactivity.class);
intent.putextra("photo", bitmap);
startactivity(intent);
}
}

mainactivity的布局文件:

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.caocong.image.widget.clipphotolayout
android:id="@+id/clip_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"/>
<button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onclick="doclick"
android:text="clip" />
</linearlayout>

以上所述是小编给大家介绍的android 仿qq头像自定义截取功能,希望对大家有所帮助