Android如何自定義頭像控件

在此輸入圖片描述

如上圖效果: 效果分析html

根據上面的效果,咱們目測須要自定義兩個控件,一個就是咱們的可自由縮放移動的ImageView,一個就是那個白色的邊框;而後一塊兒放置到一個RelativeLayout中;最後對外公佈一個裁剪的方法,返回一個Bitmap;java

讓咱們來寫代碼吧~android

首先是白色框框那個自定義View,咱們叫作ClipImageBorderViewcanvas

ClipImageBorderViewapp

分析下這個View,其實就是根據在屏幕中繪製一個正方形,正方形區域之外爲半透明,繪製這個正方形須要與屏幕左右邊距有個邊距。ide

咱們準備按以下圖繪製: 在此輸入圖片描述佈局

代碼:post

[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.}學習

咱們直接預設了一個水平方向的邊距,根據邊距計算出正方形的邊長,接下來就是按照上圖分別會一、二、三、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進行簡單的修改,修改的地方: 一、在onGlobalLayout方法中,若是圖片的寬或者高只要一個小於咱們的正方形的邊長,咱們會直接把較小的尺寸放大至正方形的邊長;若是圖片的寬和高都大於咱們的正方形的邊長,咱們僅僅把圖片移動到咱們屏幕的中央,不作縮放處理;

二、根據步驟1,咱們會得到初始的縮放比例(默認爲1.0f),而後SCALE_MID , 與 SCALE_MAX 分別爲2倍和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都須要進行抽取,且用戶在使用的時候,還須要設置爲同樣的值,總以爲有點強人所難~~

五、不同的自定義控件

如今咱們考慮下:易用性。目前爲止,其實咱們的效果已經實現了,可是須要用戶這麼寫佈局文件:

[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搞定之後,下面看下如何使用~

六、用法

一、佈局文件

[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

相關文章
相關標籤/搜索