Android 仿QQ头像自定义截取功能
程序员文章站
2024-03-05 13:47:42
看了android版qq的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识。
先看看效果:
思路分析:
这个效果可以用两个view...
看了android版qq的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识。
先看看效果:
思路分析:
这个效果可以用两个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,如下图:
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头像自定义截取功能,希望对大家有所帮助
上一篇: PHP实现的统计数据功能详解