文档章节

Android如何自定义头像控件

crossmix
 crossmix
发布于 2015/04/18 14:12
字数 2380
阅读 673
收藏 1

在此输入图片描述

如上图效果: 效果分析

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

让我们来写代码吧~

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

ClipImageBorderView

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

我们准备按如下图绘制: 在此输入图片描述

代码:

[java] view plaincopy 01.package com.zhy.view;
02.
03.import android.content.Context;
04.import android.graphics.Canvas;
05.import android.graphics.Color;
06.import android.graphics.Paint;
07.import android.graphics.Paint.Style;
08.import android.util.AttributeSet;
09.import android.util.TypedValue;
10.import android.view.View;
11./** 12. * @author zhy 13. * 14. /
15.public class ClipImageBorderView extends View
16.{
17. /
* 18. * 水平方向与View的边距 19. /
20. private int mHorizontalPadding = 20;
21. /
* 22. * 垂直方向与View的边距 23. /
24. private int mVerticalPadding;
25. /
* 26. * 绘制的矩形的宽度 27. /
28. private int mWidth;
29. /
* 30. * 边框的颜色,默认为白色 31. /
32. private int mBorderColor = Color.parseColor("#FFFFFF");
33. /
* 34. * 边框的宽度 单位dp 35. */
36. private int mBorderWidth = 1;
37.
38. private Paint mPaint;
39.
40. public ClipImageBorderView(Context context)
41. {
42. this(context, null);
43. }
44.
45. public ClipImageBorderView(Context context, AttributeSet attrs)
46. {
47. this(context, attrs, 0);
48. }
49.
50. public ClipImageBorderView(Context context, AttributeSet attrs, int defStyle)
51. {
52. super(context, attrs, defStyle);
53. // 计算padding的px
54. mHorizontalPadding = (int) TypedValue.applyDimension(
55. TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources()
56. .getDisplayMetrics());
57. mBorderWidth = (int) TypedValue.applyDimension(
58. TypedValue.COMPLEX_UNIT_DIP, mBorderWidth, getResources()
59. .getDisplayMetrics());
60. mPaint = new Paint();
61. mPaint.setAntiAlias(true);
62. }
63.
64. @Override
65. protected void onDraw(Canvas canvas)
66. {
67. super.onDraw(canvas);
68. //计算矩形区域的宽度
69. mWidth = getWidth() - 2 * mHorizontalPadding;
70. //计算距离屏幕垂直边界 的边距
71. mVerticalPadding = (getHeight() - mWidth) / 2;
72. mPaint.setColor(Color.parseColor("#aa000000"));
73. mPaint.setStyle(Style.FILL);
74. // 绘制左边1
75. canvas.drawRect(0, 0, mHorizontalPadding, getHeight(), mPaint);
76. // 绘制右边2
77. canvas.drawRect(getWidth() - mHorizontalPadding, 0, getWidth(),
78. getHeight(), mPaint);
79. // 绘制上边3
80. canvas.drawRect(mHorizontalPadding, 0, getWidth() - mHorizontalPadding,
81. mVerticalPadding, mPaint);
82. // 绘制下边4
83. canvas.drawRect(mHorizontalPadding, getHeight() - mVerticalPadding,
84. getWidth() - mHorizontalPadding, getHeight(), mPaint);
85. // 绘制外边框
86. mPaint.setColor(mBorderColor);
87. mPaint.setStrokeWidth(mBorderWidth);
88. mPaint.setStyle(Style.STROKE);
89. canvas.drawRect(mHorizontalPadding, mVerticalPadding, getWidth()
90. - mHorizontalPadding, getHeight() - mVerticalPadding, mPaint);
91.
92. }
93.
94.}

我们直接预设了一个水平方向的边距,根据边距计算出正方形的边长,接下来就是按照上图分别会1、2、3、4四个区域,最后就是绘制我们的正方形~~

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

布局文件:

[html] view plaincopy 01.<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. android:background="@drawable/a" >
06.
07. <com.zhy.view.ClipImageBorderView
08. android:id="@+id/id_clipImageLayout"
09. android:layout_width="fill_parent"
10. android:layout_height="fill_parent" />
11.
12.</RelativeLayout>

效果图:在此输入图片描述

这是图的效果。好看吧,good。 ClipZoomImageView

我们准备对我们原先的ZoomImageView进行简单的修改,修改的地方: 1、在onGlobalLayout方法中,如果图片的宽或者高只要一个小于我们的正方形的边长,我们会直接把较小的尺寸放大至正方形的边长;如果图片的宽和高都大于我们的正方形的边长,我们仅仅把图片移动到我们屏幕的中央,不做缩放处理;

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

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

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

部分代码:

[java] view plaincopy 01./** 02. * 水平方向与View的边距 03. /
04. private int mHorizontalPadding = 20;
05. /
* 06. * 垂直方向与View的边距 07. /
08. private int mVerticalPadding;
09.
10. @Override
11. public void onGlobalLayout()
12. {
13. if (once)
14. {
15. Drawable d = getDrawable();
16. if (d == null)
17. return;
18. Log.e(TAG, d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
19. // 计算padding的px
20. mHorizontalPadding = (int) TypedValue.applyDimension(
21. TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding,
22. getResources().getDisplayMetrics());
23. // 垂直方向的边距
24. mVerticalPadding = (getHeight() - (getWidth() - 2 * mHorizontalPadding)) / 2;
25.
26. int width = getWidth();
27. int height = getHeight();
28. // 拿到图片的宽和高
29. int dw = d.getIntrinsicWidth();
30. int dh = d.getIntrinsicHeight();
31. float scale = 1.0f;
32. if (dw < getWidth() - mHorizontalPadding * 2
33. && dh > getHeight() - mVerticalPadding * 2)
34. {
35. scale = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw;
36. }
37.
38. if (dh < getHeight() - mVerticalPadding * 2
39. && dw > getWidth() - mHorizontalPadding * 2)
40. {
41. scale = (getHeight() * 1.0f - mVerticalPadding * 2) / dh;
42. }
43.
44. if (dw < getWidth() - mHorizontalPadding * 2
45. && dh < getHeight() - mVerticalPadding * 2)
46. {
47. float scaleW = (getWidth() * 1.0f - mHorizontalPadding * 2)
48. / dw;
49. float scaleH = (getHeight() * 1.0f - mVerticalPadding * 2) / dh;
50. scale = Math.max(scaleW, scaleH);
51. }
52.
53. initScale = scale;
54. SCALE_MID = initScale * 2;
55. SCALE_MAX = initScale * 4;
56. Log.e(TAG, "initScale = " + initScale);
57. mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);
58. mScaleMatrix.postScale(scale, scale, getWidth() / 2,
59. getHeight() / 2);
60. // 图片移动至屏幕中心
61. setImageMatrix(mScaleMatrix);
62. once = false;
63. }
64.
65. }
66.
67. /
* 68. * 剪切图片,返回剪切后的bitmap对象 69. *
70. * @return 71. /
72. public Bitmap clip()
73. {
74. Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
75. Bitmap.Config.ARGB_8888);
76. Canvas canvas = new Canvas(bitmap);
77. draw(canvas);
78. return Bitmap.createBitmap(bitmap, mHorizontalPadding,
79. mVerticalPadding, getWidth() - 2 * mHorizontalPadding,
80. getWidth() - 2 * mHorizontalPadding);
81. }
82.
83. /
* 84. * 边界检测 85. */
86. private void checkBorder()
87. {
88.
89. RectF rect = getMatrixRectF();
90. float deltaX = 0;
91. float deltaY = 0;
92.
93. int width = getWidth();
94. int height = getHeight();
95.
96. // 如果宽或高大于屏幕,则控制范围
97. if (rect.width() >= width - 2 * mHorizontalPadding)
98. {
99. if (rect.left > mHorizontalPadding)
100. {
101. deltaX = -rect.left + mHorizontalPadding;
102. }
103. if (rect.right < width - mHorizontalPadding)
104. {
105. deltaX = width - mHorizontalPadding - rect.right;
106. }
107. }
108. if (rect.height() >= height - 2 * mVerticalPadding)
109. {
110. if (rect.top > mVerticalPadding)
111. {
112. deltaY = -rect.top + mVerticalPadding;
113. }
114. if (rect.bottom < height - mVerticalPadding)
115. {
116. deltaY = height - mVerticalPadding - rect.bottom;
117. }
118. }
119. mScaleMatrix.postTranslate(deltaX, deltaY);
120.
121. }

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

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

5、不一样的自定义控件

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

[html] view plaincopy 01.<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. android:background="#aaaaaa" >
06.
07. <com.zhy.view.ZoomImageView
08. android:id="@+id/id_zoomImageView"
09. android:layout_width="fill_parent"
10. android:layout_height="fill_parent"
11. android:scaleType="matrix"
12. android:src="@drawable/a" />
13.
14. <com.zhy.view.ClipImageView
15. android:layout_width="fill_parent"
16. android:layout_height="fill_parent" />
17.
18.</RelativeLayout>

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

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

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

、ClipImageLayout

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

完整的ClipImageLayout代码:

[java] view plaincopy 01.package com.zhy.view;
02.
03.import android.content.Context;
04.import android.graphics.Bitmap;
05.import android.util.AttributeSet;
06.import android.util.TypedValue;
07.import android.widget.RelativeLayout;
08.
09.import com.zhy.clippic.R;
10./** 11. * zhy 12. * @author zhy 13. * 14. /
15.public class ClipImageLayout extends RelativeLayout
16.{
17.
18. private ClipZoomImageView mZoomImageView;
19. private ClipImageBorderView mClipImageView;
20.
21. /
* 22. * 这里测试,直接写死了大小,真正使用过程中,可以提取为自定义属性 23. /
24. private int mHorizontalPadding = 20;
25.
26. public ClipImageLayout(Context context, AttributeSet attrs)
27. {
28. super(context, attrs);
29.
30. mZoomImageView = new ClipZoomImageView(context);
31. mClipImageView = new ClipImageBorderView(context);
32.
33. android.view.ViewGroup.LayoutParams lp = new LayoutParams(
34. android.view.ViewGroup.LayoutParams.MATCH_PARENT,
35. android.view.ViewGroup.LayoutParams.MATCH_PARENT);
36.
37. /
* 38. * 这里测试,直接写死了图片,真正使用过程中,可以提取为自定义属性 39. /
40. mZoomImageView.setImageDrawable(getResources().getDrawable(
41. R.drawable.a));
42.
43. this.addView(mZoomImageView, lp);
44. this.addView(mClipImageView, lp);
45.
46.
47. // 计算padding的px
48. mHorizontalPadding = (int) TypedValue.applyDimension(
49. TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding, getResources()
50. .getDisplayMetrics());
51. mZoomImageView.setHorizontalPadding(mHorizontalPadding);
52. mClipImageView.setHorizontalPadding(mHorizontalPadding);
53. }
54.
55. /
* 56. * 对外公布设置边距的方法,单位为dp 57. *
58. * @param mHorizontalPadding 59. /
60. public void setHorizontalPadding(int mHorizontalPadding)
61. {
62. this.mHorizontalPadding = mHorizontalPadding;
63. }
64.
65. /
* 66. * 裁切图片 67. *
68. * @return 69. */
70. public Bitmap clip()
71. {
72. return mZoomImageView.clip();
73. }
74.
75.}

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

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

6、用法

1、布局文件

[html] view plaincopy 01.<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. android:background="#aaaaaa" >
06.
07. <com.zhy.view.ClipImageLayout
08. android:id="@+id/id_clipImageLayout"
09. android:layout_width="fill_parent"
10. android:layout_height="fill_parent" />
11.
12.</RelativeLayout>

、MainActivity

[java] view plaincopy 01.package com.zhy.clippic;
02.
03.import java.io.ByteArrayOutputStream;
04.
05.import android.app.Activity;
06.import android.content.Intent;
07.import android.graphics.Bitmap;
08.import android.os.Bundle;
09.import android.view.Menu;
10.import android.view.MenuItem;
11.
12.import com.zhy.view.ClipImageLayout;
13.
14.public class MainActivity extends Activity
15.{
16. private ClipImageLayout mClipImageLayout;
17.
18. @Override
19. protected void onCreate(Bundle savedInstanceState)
20. {
21. super.onCreate(savedInstanceState);
22. setContentView(R.layout.activity_main);
23.
24. mClipImageLayout = (ClipImageLayout) findViewById(R.id.id_clipImageLayout);
25.
26. }
27.
28. @Override
29. public boolean onCreateOptionsMenu(Menu menu)
30. {
31. getMenuInflater().inflate(R.menu.main, menu);
32. return true;
33. }
34.
35. @Override
36. public boolean onOptionsItemSelected(MenuItem item)
37. {
38. switch (item.getItemId())
39. {
40. case R.id.id_action_clip:
41. Bitmap bitmap = mClipImageLayout.clip();
42.
43. ByteArrayOutputStream baos = new ByteArrayOutputStream();
44. bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
45. byte[] datas = baos.toByteArray();
46.
47. Intent intent = new Intent(this, ShowImageActivity.class);
48. intent.putExtra("bitmap", datas);
49. startActivity(intent);
50.
51. break;
52. }
53. return super.onOptionsItemSelected(item);
54. }
55.}

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

看一下眼menu的xml

[html] view plaincopy 01.<menu xmlns:android="http://schemas.android.com/apk/res/android" >
02.
03. <item
04. android:id="@+id/id_action_clip"
05. android:icon="@drawable/actionbar_clip_icon"
06. android:showAsAction="always|withText"
07. android:title="裁切"/>
08.
09.</menu>

、ShowImageActivity

[java] view plaincopy 01.package com.zhy.clippic;
02.
03.
04.import android.app.Activity;
05.import android.graphics.Bitmap;
06.import android.graphics.BitmapFactory;
07.import android.os.Bundle;
08.import android.widget.ImageView;
09.
10.
11.public class ShowImageActivity extends Activity
12.{
13. private ImageView mImageView;
14.
15.
16. @Override
17. protected void onCreate(Bundle savedInstanceState)
18. {
19. super.onCreate(savedInstanceState);
20. setContentView(R.layout.show);
21.
22.
23. mImageView = (ImageView) findViewById(R.id.id_showImage);
24. byte[] b = getIntent().getByteArrayExtra("bitmap");
25. Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
26. if (bitmap != null)
27. {
28. mImageView.setImageBitmap(bitmap);
29. }
30. }
31.}

layout/show.xml

[html] view plaincopy 01.<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. android:background="#ffffff" >
06.
07. <ImageView
08. android:id="@+id/id_showImage"
09. android:layout_width="wrap_content"
10. android:layout_height="wrap_content"
11. android:layout_centerInParent="true"
12. android:src="@drawable/tbug"
13. />
14.
15.</RelativeLayout>

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

在此输入图片描述

当然你还可以添加更多的功能。 end

© 著作权归作者所有

crossmix
粉丝 72
博文 305
码字总数 645711
作品 0
广州
高级程序员
私信 提问
2017 我用 5 个月分享了 98 篇优秀博文

对上半年所分享的文章进行一个整理,很多读者当时忘记了收藏,以致于查找一篇历史文章很费劲,因此在这里顺便做下记录。目前就分下下面几个大类,没有更多细分,已基本可以查找了。 如果觉得...

code小生
2018/10/30
0
0
Android 自定义组合控件小结

引言 接触Android UI开发的这段时间以来,对自定义组合控件有了一定的了解,为此小结一下,本文小结内容主要讨论的是如何使用Android SDK提供的布局和控件组成一个功能完整组合控件并将其封装...

程序袁_绪龙
2014/09/24
310
0
如何自定义LinearLayout

最近通过看别人代码和网上搜索,发现现在自定义LinearLayout的方式有两种。第一种是在扩展的LinearLayout构造函数中使用Inflater加载一个布局,并从中提取出相关的UI组件进行封装,形成一个独...

Kingguary
2012/06/25
5.1K
0
Android自定义控件(三)继承控件

继承控件是我们Android开发中最重要也是最常使用的控件,它不同于自绘控件和组合控件,它不需要我们自己去重新去实现一个控件。面向对象的一个重要特性就是继承,相信各位同学对继承的了解都...

Jack_1900
2014/07/13
890
3
【OSC手机App技术解析】- 列表异步线程加载图片

手机客户端以列表形式展示数据是非常常见的一种方式。然而列表中要显示图片(比如:头像)就要采用异步线程加载的方式,这样做是为了防止加载图片数据的时候,花费时间过长,阻塞UI线程,从而...

迷途d书童
2012/06/29
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
今天
9
0
golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架...

非正式解决方案
今天
6
0
前端——使用base64编码在页面嵌入图片

因为页面中插入一个图片都要写明图片的路径——相对路径或者绝对路径。而除了具体的网站图片的图片地址,如果是在自己电脑文件夹里的图片,当我们的HTML文件在别人电脑上打开的时候图片则由于...

被毒打的程序猿
今天
8
0
Flutter 系列之Dart语言概述

Dart语言与其他语言究竟有什么不同呢?在已有的编程语言经验的基础上,我们该如何快速上手呢?本篇文章从编程语言中最重要的组成部分,也就是基础语法与类型变量出发,一起来学习Dart吧 一、...

過愙
今天
5
0
rime设置为默认简体

转载 https://github.com/ModerRAS/ModerRAS.github.io/blob/master/_posts/2018-11-07-rime%E8%AE%BE%E7%BD%AE%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%AE%80%E4%BD%93.md 写在开始 我的Arch Linux上......

zhenruyan
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部