1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Android 高仿微信头像截取 打造不一样的自定义控件

Android 高仿微信头像截取 打造不一样的自定义控件

时间:2023-08-19 20:22:23

相关推荐

Android 高仿微信头像截取 打造不一样的自定义控件

转载请表明出处:/lmj623565791/article/details/39761281,本文出自:【张鸿洋的博客】

1、概述

前面已经写了关于检测手势识别的文章,如果不了解可以参考:Android 手势检测实战 打造支持缩放平移的图片预览效果(下)。首先本篇文章,将对之前博客的ZoomImageView代码进行些许的修改与改善,然后用到我们的本篇博客中去,实现仿微信的头像截取功能,当然了,个人觉得微信的截取头像功能貌似做得不太好,本篇博客准备去其糟粕,取其精华;最后还会见识到不一样的自定义控件的方式,也是在本人博客中首次出现,如果有兴趣可以读完本篇博客,希望可以启到抛砖引玉的效果。

2、效果分析

1、效果图:

我们来看看妹子的项链,嗯,妹子项链还是不错的~

2、效果分析

根据上面的效果,我们目测需要自定义两个控件,一个就是我们的可自由缩放移动的ImageView,一个就是那个白色的边框;然后一起放置到一个RelativeLayout中;最后对外公布一个裁剪的方法,返回一个Bitmap;

暂时的分析就这样,下面我们来写代码~

首先是白色框框那个自定义View,我们叫做ClipImageBorderView

3、ClipImageBorderView

分析下这个View,其实就是根据在屏幕中绘制一个正方形,正方形区域以外为半透明,绘制这个正方形需要与屏幕左右边距有个边距。

我们准备按如下图绘制:

按顺序在View的onDraw里面绘制上图中:1、2、3、4,四个半透明的区域,然后在中间正方形区域绘制一个正方形

下面看下代码:

[java]view plaincopypackagecom.zhy.view; importandroid.content.Context; importandroid.graphics.Canvas; importandroid.graphics.Color; importandroid.graphics.Paint; importandroid.graphics.Paint.Style; importandroid.util.AttributeSet; importandroid.util.TypedValue; importandroid.view.View; /** *@authorzhy * */ publicclassClipImageBorderViewextendsView { /** *水平方向与View的边距 */ privateintmHorizontalPadding=20; /** *垂直方向与View的边距 */ privateintmVerticalPadding; /** *绘制的矩形的宽度 */ privateintmWidth; /** *边框的颜色,默认为白色 */ privateintmBorderColor=Color.parseColor("#FFFFFF"); /** *边框的宽度单位dp */ privateintmBorderWidth=1; privatePaintmPaint; publicClipImageBorderView(Contextcontext) { this(context,null); } publicClipImageBorderView(Contextcontext,AttributeSetattrs) { this(context,attrs,0); } publicClipImageBorderView(Contextcontext,AttributeSetattrs,intdefStyle) { super(context,attrs,defStyle); //计算padding的px mHorizontalPadding=(int)TypedValue.applyDimension( PLEX_UNIT_DIP,mHorizontalPadding,getResources() .getDisplayMetrics()); mBorderWidth=(int)TypedValue.applyDimension( PLEX_UNIT_DIP,mBorderWidth,getResources() .getDisplayMetrics()); mPaint=newPaint(); mPaint.setAntiAlias(true); } @Override protectedvoidonDraw(Canvascanvas) { super.onDraw(canvas); //计算矩形区域的宽度 mWidth=getWidth()-2*mHorizontalPadding; //计算距离屏幕垂直边界的边距 mVerticalPadding=(getHeight()-mWidth)/2; mPaint.setColor(Color.parseColor("#aa000000")); mPaint.setStyle(Style.FILL); //绘制左边1 canvas.drawRect(0,0,mHorizontalPadding,getHeight(),mPaint); //绘制右边2 canvas.drawRect(getWidth()-mHorizontalPadding,0,getWidth(), getHeight(),mPaint); //绘制上边3 canvas.drawRect(mHorizontalPadding,0,getWidth()-mHorizontalPadding, mVerticalPadding,mPaint); //绘制下边4 canvas.drawRect(mHorizontalPadding,getHeight()-mVerticalPadding, getWidth()-mHorizontalPadding,getHeight(),mPaint); //绘制外边框 mPaint.setColor(mBorderColor); mPaint.setStrokeWidth(mBorderWidth); mPaint.setStyle(Style.STROKE); canvas.drawRect(mHorizontalPadding,mVerticalPadding,getWidth() -mHorizontalPadding,getHeight()-mVerticalPadding,mPaint); } } 我们直接预设了一个水平方向的边距,根据边距计算出正方形的边长,接下来就是按照上图分别会1、2、3、4四个区域,最后就是绘制我们的正方形~~

代码还是很简单的~~我们的ClipImageBorderView就搞定了,我们决定来测试一下:

布局文件:

[html]view plaincopy<RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/a"> <com.zhy.view.ClipImageBorderView android:id="@+id/id_clipImageLayout" android:layout_width="fill_parent" android:layout_height="fill_parent"/> </RelativeLayout>

效果图:

故意放了个背景,没撒用,就是为了能看出效果,可以看到我们的框框绘制的还是蛮不错的~~嗯,这个框框距离屏幕左右两侧的距离应该抽取出来,嗯,后面再说~

4、ClipZoomImageView

我们准备对我们原先的ZoomImageView进行简单的修改,修改的地方:

1、在onGlobalLayout方法中,如果图片的宽或者高只要一个小于我们的正方形的边长,我们会直接把较小的尺寸放大至正方形的边长;如果图片的宽和高都大于我们的正方形的边长,我们仅仅把图片移动到我们屏幕的中央,不做缩放处理;

2、根据步骤1,我们会获得初始的缩放比例(默认为1.0f),然后SCALE_MID , 与SCALE_MAX 分别为2倍和4倍的初始化缩放比例。

3、图片在移动过程中的边界检测完全根据正方形的区域,图片不会在移动过程中与正方形区域产生内边距

4、对外公布一个裁切的方法

部分代码:

[java]view plaincopy/** *水平方向与View的边距 */ privateintmHorizontalPadding=20; /** *垂直方向与View的边距 */ privateintmVerticalPadding; @Override publicvoidonGlobalLayout() { if(once) { Drawabled=getDrawable(); if(d==null) return; Log.e(TAG,d.getIntrinsicWidth()+","+d.getIntrinsicHeight()); //计算padding的px mHorizontalPadding=(int)TypedValue.applyDimension( PLEX_UNIT_DIP,mHorizontalPadding, getResources().getDisplayMetrics()); //垂直方向的边距 mVerticalPadding=(getHeight()-(getWidth()-2*mHorizontalPadding))/2; intwidth=getWidth(); intheight=getHeight(); //拿到图片的宽和高 intdw=d.getIntrinsicWidth(); intdh=d.getIntrinsicHeight(); floatscale=1.0f; if(dw<getWidth()-mHorizontalPadding*2 &&dh>getHeight()-mVerticalPadding*2) { scale=(getWidth()*1.0f-mHorizontalPadding*2)/dw; } if(dh<getHeight()-mVerticalPadding*2 &&dw>getWidth()-mHorizontalPadding*2) { scale=(getHeight()*1.0f-mVerticalPadding*2)/dh; } if(dw<getWidth()-mHorizontalPadding*2 &&dh<getHeight()-mVerticalPadding*2) { floatscaleW=(getWidth()*1.0f-mHorizontalPadding*2) /dw; floatscaleH=(getHeight()*1.0f-mVerticalPadding*2)/dh; scale=Math.max(scaleW,scaleH); } initScale=scale; SCALE_MID=initScale*2; SCALE_MAX=initScale*4; Log.e(TAG,"initScale="+initScale); mScaleMatrix.postTranslate((width-dw)/2,(height-dh)/2); mScaleMatrix.postScale(scale,scale,getWidth()/2, getHeight()/2); //图片移动至屏幕中心 setImageMatrix(mScaleMatrix); once=false; } } /** *剪切图片,返回剪切后的bitmap对象 * *@return */ publicBitmapclip() { Bitmapbitmap=Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888); Canvascanvas=newCanvas(bitmap); draw(canvas); returnBitmap.createBitmap(bitmap,mHorizontalPadding, mVerticalPadding,getWidth()-2*mHorizontalPadding, getWidth()-2*mHorizontalPadding); } /** *边界检测 */ privatevoidcheckBorder() { RectFrect=getMatrixRectF(); floatdeltaX=0; floatdeltaY=0; intwidth=getWidth(); intheight=getHeight(); //如果宽或高大于屏幕,则控制范围 if(rect.width()>=width-2*mHorizontalPadding) { if(rect.left>mHorizontalPadding) { deltaX=-rect.left+mHorizontalPadding; } if(rect.right<width-mHorizontalPadding) { deltaX=width-mHorizontalPadding-rect.right; } } if(rect.height()>=height-2*mVerticalPadding) { if(rect.top>mVerticalPadding) { deltaY=-rect.top+mVerticalPadding; } if(rect.bottom<height-mVerticalPadding) { deltaY=height-mVerticalPadding-rect.bottom; } } mScaleMatrix.postTranslate(deltaX,deltaY); }

这里贴出了改变的代码,完整的代码就不贴了,太长了,如果大家学习过前面的博客应该也会比较熟悉,若没有也没事,后面会提供源码。

贴代码的目的,第一让大家看下我们改变了哪些;第二,我想暴露出我们代码中的问题,我们设置了一个这样的变量:mHorizontalPadding = 20;这个是手动和ClipImageBorderView里面的成员变量mHorizontalPadding写的一致,也就是说这个变量,两个自定义的View都需要使用且需要相同的值,目前我们的做法,写死且每个View各自定义一个。这种做法不用说,肯定不好,即使抽取成自定义属性,两个View都需要进行抽取,且用户在使用的时候,还需要设置为一样的值,总觉得有点强人所难~~

5、不一样的自定义控件

现在我们考虑下:易用性。目前为止,其实我们的效果已经实现了,但是需要用户这么写布局文件:

[html]view plaincopy<RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aaaaaa"> <com.zhy.view.ZoomImageView android:id="@+id/id_zoomImageView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="matrix" android:src="@drawable/a"/> <com.zhy.view.ClipImageView android:layout_width="fill_parent" android:layout_height="fill_parent"/> </RelativeLayout>

然后这两个类中都有一个mHorizontalPadding变量,且值一样,上面也说过,即使抽取成自定义变量,也需要在布局文件中每个View中各写一次。so, we need change . 这样的耦合度太夸张了,且使用起来蹩脚。

于是乎,我决定把这两个控件想办法整到一起,用户使用时只需要声明一个控件:

怎么做呢,我们使用组合的思想来自定义控件,我们再声明一个控件,继承子RelativeLayout,然后在这个自定义RelativeLayout中通过代码添加这两个自定义的布局,并且设置一些公用的属性,具体我们就开始行动。

1、ClipImageLayout

我们自定义一个RelativeLayout叫做ClipImageLayout,用于放置我们的两个自定义View,并且由ClipImageLayout进行设置边距,然后传给它内部的两个View,这样的话,跟用户交互的就一个ClipImageLayout,用户只需要设置一次边距即可。

完整的ClipImageLayout代码:

[java]view plaincopypackagecom.zhy.view; importandroid.content.Context; importandroid.graphics.Bitmap; importandroid.util.AttributeSet; importandroid.util.TypedValue; importandroid.widget.RelativeLayout; importcom.zhy.clippic.R; /** *zhy *@authorzhy * */ publicclassClipImageLayoutextendsRelativeLayout { privateClipZoomImageViewmZoomImageView; privateClipImageBorderViewmClipImageView; /** *这里测试,直接写死了大小,真正使用过程中,可以提取为自定义属性 */ privateintmHorizontalPadding=20; publicClipImageLayout(Contextcontext,AttributeSetattrs) { super(context,attrs); mZoomImageView=newClipZoomImageView(context); mClipImageView=newClipImageBorderView(context); android.view.ViewGroup.LayoutParamslp=newLayoutParams( android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.MATCH_PARENT); /** *这里测试,直接写死了图片,真正使用过程中,可以提取为自定义属性 */ mZoomImageView.setImageDrawable(getResources().getDrawable( R.drawable.a)); this.addView(mZoomImageView,lp); this.addView(mClipImageView,lp); //计算padding的px mHorizontalPadding=(int)TypedValue.applyDimension( PLEX_UNIT_DIP,mHorizontalPadding,getResources() .getDisplayMetrics()); mZoomImageView.setHorizontalPadding(mHorizontalPadding); mClipImageView.setHorizontalPadding(mHorizontalPadding); } /** *对外公布设置边距的方法,单位为dp * *@parammHorizontalPadding */ publicvoidsetHorizontalPadding(intmHorizontalPadding) { this.mHorizontalPadding=mHorizontalPadding; } /** *裁切图片 * *@return */ publicBitmapclip() { returnmZoomImageView.clip(); } }

可以看到,现在用户需要使用头像裁切功能只需要声明下ClipImageLayout即可,完全避免了上述我们描述的问题,我们对用户屏蔽了两个真正实现的类。这个也是自定义控件的一种方式,希望可以借此抛砖引玉,大家能够更加合理的设计出自己的控件~~

好了,我们的ClipImageLayout搞定以后,下面看下如何使用~

6、用法

1、布局文件

[html]view plaincopy<RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aaaaaa"> <com.zhy.view.ClipImageLayout android:id="@+id/id_clipImageLayout" android:layout_width="fill_parent" android:layout_height="fill_parent"/> </RelativeLayout>

2、MainActivity

[java]view plaincopypackagecom.zhy.clippic; importjava.io.ByteArrayOutputStream; importandroid.app.Activity; importandroid.content.Intent; importandroid.graphics.Bitmap; importandroid.os.Bundle; importandroid.view.Menu; importandroid.view.MenuItem; importcom.zhy.view.ClipImageLayout; publicclassMainActivityextendsActivity { privateClipImageLayoutmClipImageLayout; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mClipImageLayout=(ClipImageLayout)findViewById(R.id.id_clipImageLayout); } @Override publicbooleanonCreateOptionsMenu(Menumenu) { getMenuInflater().inflate(R.menu.main,menu); returntrue; } @Override publicbooleanonOptionsItemSelected(MenuItemitem) { switch(item.getItemId()) { caseR.id.id_action_clip: Bitmapbitmap=mClipImageLayout.clip(); ByteArrayOutputStreambaos=newByteArrayOutputStream(); press(pressFormat.JPEG,100,baos); byte[]datas=baos.toByteArray(); Intentintent=newIntent(this,ShowImageActivity.class); intent.putExtra("bitmap",datas); startActivity(intent); break; } returnsuper.onOptionsItemSelected(item); } }

我们在menu里面体检了一个裁切的按钮,点击后把裁切好的图片传递给我们的ShowImageActivity

看一下眼menu的xml

[html]view plaincopy<menuxmlns:android="/apk/res/android"> <item android:id="@+id/id_action_clip" android:icon="@drawable/actionbar_clip_icon" android:showAsAction="always|withText" android:title="裁切"/> </menu>

3、ShowImageActivity

[java]view plaincopypackagecom.zhy.clippic; importandroid.app.Activity; importandroid.graphics.Bitmap; importandroid.graphics.BitmapFactory; importandroid.os.Bundle; importandroid.widget.ImageView; publicclassShowImageActivityextendsActivity { privateImageViewmImageView; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.show); mImageView=(ImageView)findViewById(R.id.id_showImage); byte[]b=getIntent().getByteArrayExtra("bitmap"); Bitmapbitmap=BitmapFactory.decodeByteArray(b,0,b.length); if(bitmap!=null) { mImageView.setImageBitmap(bitmap); } } }

layout/show.xml

[html]view plaincopy<RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff"> <ImageView android:id="@+id/id_showImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/tbug" /> </RelativeLayout>

好了,到此我们的高仿微信头像截取功能 就已经结束了~~希望大家可以从本篇博客中可以领悟到something~

最后我们把ClipImageLayout里面的mHorizontalPadding设置为50,贴个静态效果图~

ok ~~

源码点击下载

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。