首页 > 代码库 > Android 高仿微信头像截取 打造不一样的自己定义控件
Android 高仿微信头像截取 打造不一样的自己定义控件
1、概述
前面已经写了关于检測手势识别的文章。假设不了解能够參考:Android 手势检測实战 打造支持缩放平移的图片预览效果(下)。首先本篇文章,将对之前博客的ZoomImageView代码进行些许的改动与改善,然后用到我们的本篇博客中去,实现仿微信的头像截取功能。当然了。个人觉得微信的截取头像功能貌似做得不太好。本篇博客准备去其糟粕,取其精华;最后还会见识到不一样的自己定义控件的方式,也是在本人博客中首次出现。假设有兴趣能够读完本篇博客,希望能够启到抛砖引玉的效果。
2、效果分析
1、效果图:
我们来看看妹子的项链,嗯。妹子项链还是不错的~
2、效果分析
依据上面的效果。我们目測须要自己定义两个控件,一个就是我们的可自由缩放移动的ImageView,一个就是那个白色的边框。然后一起放置到一个RelativeLayout中;最后对外发布一个裁剪的方法,返回一个Bitmap。
临时的分析就这样。以下我们来写代码~
首先是白色框框那个自己定义View,我们叫做ClipImageBorderView
3、ClipImageBorderView
分析下这个View,事实上就是依据在屏幕中绘制一个正方形,正方形区域以外为半透明。绘制这个正方形须要与屏幕左右边距有个边距。
我们准备按例如以下图绘制:
按顺序在View的onDraw里面绘制上图中:1、2、3、4。四个半透明的区域。然后在中间正方形区域绘制一个正方形
以下看下代码:
package com.zhy.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; /** * @author zhy * */ public class ClipImageBorderView extends View { /** * 水平方向与View的边距 */ private int mHorizontalPadding = 20; /** * 垂直方向与View的边距 */ private int mVerticalPadding; /** * 绘制的矩形的宽度 */ private int mWidth; /** * 边框的颜色,默觉得白色 */ private int mBorderColor = Color.parseColor("#FFFFFF"); /** * 边框的宽度 单位dp */ private int mBorderWidth = 1; private Paint mPaint; public ClipImageBorderView(Context context) { this(context, null); } public ClipImageBorderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ClipImageBorderView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 计算padding的px mHorizontalPadding = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources() .getDisplayMetrics()); mBorderWidth = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, mBorderWidth, getResources() .getDisplayMetrics()); mPaint = new Paint(); mPaint.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { 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四个区域,最后就是绘制我们的正方形~~
代码还是非常easy的~~我们的ClipImageBorderView就搞定了。我们决定来測试一下:
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/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、对外发布一个裁切的方法
部分代码:
/** * 水平方向与View的边距 */ private int mHorizontalPadding = 20; /** * 垂直方向与View的边距 */ private int mVerticalPadding; @Override public void onGlobalLayout() { if (once) { Drawable d = getDrawable(); if (d == null) return; Log.e(TAG, d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight()); // 计算padding的px mHorizontalPadding = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources().getDisplayMetrics()); // 垂直方向的边距 mVerticalPadding = (getHeight() - (getWidth() - 2 * mHorizontalPadding)) / 2; int width = getWidth(); int height = getHeight(); // 拿到图片的宽和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); float scale = 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) { float scaleW = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw; float scaleH = (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 */ public Bitmap clip() { Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); draw(canvas); return Bitmap.createBitmap(bitmap, mHorizontalPadding, mVerticalPadding, getWidth() - 2 * mHorizontalPadding, getWidth() - 2 * mHorizontalPadding); } /** * 边界检測 */ private void checkBorder() { RectF rect = getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = 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、不一样的自己定义控件
如今我们考虑下:易用性。
眼下为止,事实上我们的效果已经实现了,可是须要用户这么写布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/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="http://www.mamicode.com/@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代码:
package com.zhy.view; import android.content.Context; import android.graphics.Bitmap; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.RelativeLayout; import com.zhy.clippic.R; /** * zhy * @author zhy * */ public class ClipImageLayout extends RelativeLayout { private ClipZoomImageView mZoomImageView; private ClipImageBorderView mClipImageView; /** * 这里測试,直接写死了大小,真正使用过程中,能够提取为自己定义属性 */ private int mHorizontalPadding = 20; public ClipImageLayout(Context context, AttributeSet attrs) { super(context, attrs); mZoomImageView = new ClipZoomImageView(context); mClipImageView = new ClipImageBorderView(context); android.view.ViewGroup.LayoutParams lp = new LayoutParams( 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( TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources() .getDisplayMetrics()); mZoomImageView.setHorizontalPadding(mHorizontalPadding); mClipImageView.setHorizontalPadding(mHorizontalPadding); } /** * 对外发布设置边距的方法,单位为dp * * @param mHorizontalPadding */ public void setHorizontalPadding(int mHorizontalPadding) { this.mHorizontalPadding = mHorizontalPadding; } /** * 裁切图片 * * @return */ public Bitmap clip() { return mZoomImageView.clip(); } }
能够看到,如今用户须要使用头像裁切功能仅仅须要声明下ClipImageLayout就可以,全然避免了上述我们描写叙述的问题。我们对用户屏蔽了两个真正实现的类。
这个也是自己定义控件的一种方式。希望能够借此抛砖引玉,大家能够更加合理的设计出自己的控件~~
好了,我们的ClipImageLayout搞定以后。以下看下怎样使用~
6、使用方法
1、布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/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
package com.zhy.clippic; import java.io.ByteArrayOutputStream; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import com.zhy.view.ClipImageLayout; public class MainActivity extends Activity { private ClipImageLayout mClipImageLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mClipImageLayout = (ClipImageLayout) findViewById(R.id.id_clipImageLayout); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.id_action_clip: Bitmap bitmap = mClipImageLayout.clip(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); byte[] datas = baos.toByteArray(); Intent intent = new Intent(this, ShowImageActivity.class); intent.putExtra("bitmap", datas); startActivity(intent); break; } return super.onOptionsItemSelected(item); } }
我们在menu里面体检了一个裁切的button。点击后把裁切好的图片传递给我们的ShowImageActivity
看一下眼menu的xml
<menu xmlns:android="http://schemas.android.com/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
package com.zhy.clippic; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.widget.ImageView; public class ShowImageActivity extends Activity { private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.show); mImageView = (ImageView) findViewById(R.id.id_showImage); byte[] b = getIntent().getByteArrayExtra("bitmap"); Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } } }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/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="http://www.mamicode.com/@drawable/tbug" /> </RelativeLayout>
最后我们把ClipImageLayout里面的mHorizontalPadding设置为50。贴个静态效果图~
ok ~~
源代码点击下载
博主部分视频已经上线,假设你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):
1、高仿微信5.2.1主界面及消息提醒
2、高仿QQ5.0側滑
Android 高仿微信头像截取 打造不一样的自己定义控件