Android 高仿微信头像截取 打造不一样的自定义控件
转载请表明出处:http://blog.csdn.net/lmj623565791/article/details/39761281,本文出自:【张鸿洋的博客】
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四个区域,最后就是绘制我们的正方形~~
代码还是很简单的~~我们的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="@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里面体检了一个裁切的按钮,点击后把裁切好的图片传递给我们的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);
- }
- }
- }
layout/show.xml
- <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="@drawable/tbug"
- />
- </RelativeLayout>
好了,到此我们的 高仿微信头像截取功能 就已经结束了~~希望大家可以从本篇博客中可以领悟到something~
最后我们把ClipImageLayout里面的mHorizontalPadding设置为50,贴个静态效果图~
ok ~~
Android 高仿微信头像截取 打造不一样的自定义控件的更多相关文章
- 安卓高仿QQ头像截取升级版
观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图: 为了适应大家不同需求,这次打了两个包,及上图 ...
- 高仿QQ头像截取
花费了半天时间,把 仿QQ头像截取的方法整理了下,并制作了一个demo以供大家参考,基本上实现了qq中我的资料界面上(包括背景透明化,上滑标题栏显示,下拉隐藏等)的大致效果,先上图看效果吧: 支持的功 ...
- Android 高仿微信6.0主界面 带你玩转切换图标变色
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41087219,本文出自:[张鸿洋的博客] 1.概述 学习Android少不了模仿 ...
- Android 高仿微信语音聊天页面高斯模糊效果
目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...
- Android高仿微信(一)——如何消除启动时的白屏
默认情况下,APP启动时会先把屏幕刷成白色,然后才绘制第一个Activity中的View,这两个步骤之间的延迟会造成启动后先看到白屏(时间大概为1秒左右).时间不长,但是我们也看到,一般的APP时不存 ...
- Android 高仿微信实时聊天 基于百度云推送
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天终于有幸利用百 ...
- android高仿微信拍照、多选、预览、删除(去除相片)相冊功能
先声明授人与鱼不如授人与渔,仅仅能提供一个思路,当然须要源代码的同学能够私下有偿问我要源代码:QQ:508181017 工作了将近三年时间了,一直没正儿八经的研究系统自带的相冊和拍照,这回来个高仿微信 ...
- Android 高仿微信即时聊天 百度云为基础的推
转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天最终有幸利用百 ...
- Android 高仿微信支付键盘
现在很多app的支付.输入密码功能,都已经开始使用自定义数字键盘,不仅更加方便.其效果着实精致. 下面带着大家学习下,如何高仿微信的数字键盘,可以拿来直接用在自身的项目中. 先看下效果图: 1. 自定 ...
随机推荐
- SQL2008安装重启失败
我今天安装SQL2008的一些问题经历SQL2008安装重启失败大致出错信息如下:RebootRequiredCheck 检查是否需要挂起计算机重新启动.挂起重新启动会导致安装程序失败. 失败 需要重 ...
- 浅述Oracle分布式事务概念
着系统的复杂性不断增加,我们所面对的分布式系统渐渐增加.分布式文件系统.分布式消息队列系统等等层出不穷,在一些行业特别是互联网行业应用广泛.分布式数据库也是目前使用比较常用的分布式系统之一. 简单来说 ...
- 读书笔记_Effective_C++_条款二十三:宁以non-member、non-friend替换member函数
有下面一种情况 class A { private: int a; int b; public: A(int x, int y) :a(x), b(y){} void a_display(){ cou ...
- c# 基础复习1
1. 类和对象 1.1 类和对象的概念 类:对象的类型,它不同于 int 等基本数据类型,因为类具有行为:也可以说是具有相 同特征和行为的一组对象的集合. 对象:对象是一个个你能看得见,摸得着的实体, ...
- 带你深入了解Web站点数据库的分布存储
作者:finalbsd原载: http://www.sanotes.net/html/y2009/358.html 在Web 2.0时代,网站将会经常面临着快速增加的访问量,但是我们的应用如何满足用户 ...
- Android再学习-便签开发小结-20141119
这几天的便签开发,首先遇到的问题就是数据库操作问题.现在已经可以读写数据库了,并能将数据放在正确的位置显示. 专门建立了一个数据库操作的包,命名为"...database".新建一 ...
- [算法]分治算法(Divide and Conquer)
转载请注明:http://www.cnblogs.com/StartoverX/p/4575744.html 分治算法 在计算机科学中,分治法是建基于多项分支递归的一种很重要的算法范式.字面上的解释是 ...
- spi ssp
SSP(Synchronous Serial Port 同步串行口)某些微处理器所含有的一个通信模块(或支持的通信模式),用来和外围串行部件或其他微处理器进行通信,这些外围部件可以是串行E2PROM. ...
- 使用OC和swift创建系统自带的刷新界面
使用OC和swift创建系统自带的刷新界面 一:swift刷新界面代码: import UIKit class ViewController: UITableViewController { // 用 ...
- DataTables自定义事件
$(document).ready(function() { var eventFired = function(type) { var n = $('#demo_info')[0]; n.inner ...