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);
- }
- }
程式碼還是很簡單的~~我們的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);
- }
- }
- }
- <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>
最後我們把ClipImageLayout裡面的mHorizontalPadding設定為50,貼個靜態效果圖~
ok ~~
相關文章
- Android 仿釘釘、微信 群聊組合頭像Android
- Android 自定義圓形旋轉進度條,仿微博頭像載入效果Android
- 擷取圖片生成頭像外掛
- TP開發的 HTML5頭像擷取案例,會員頭像功能HTML
- Android 仿微信/支付寶 字型大小 調整控制元件Android控制元件
- 微信小程式Tree自定義控制元件實現微信小程式控制元件
- 微信小程式--自定義radio元件樣式微信小程式元件
- Android 仿微信, QQ 裁剪Android
- WPF 仿語音播放 自定義控制元件控制元件
- 微信小程式 獲取微信暱稱頭像 獲取openid 封裝請求post微信小程式封裝
- 高仿微信聊天介面長按彈框樣式
- vue 高仿微信即時 IM 聊天|仿微信 vue+h5 版|仿微信介面VueH5
- 【Android】自定義樹形控制元件Android控制元件
- 微信小程式獲取base64頭像上傳微信小程式
- UWP自定義ToggleButton控制元件的樣式控制元件
- android仿微信表情雨下落!Android
- 擷取第一個字用php生成一個頭像PHP
- vue自定義指令擷取圖片中心顯示Vue
- 仿微信評論控制元件封裝控制元件封裝
- 微信小程式-自定義placeholder顏色和樣式微信小程式
- Flutter仿微信,支付寶密碼輸入框+自定義鍵盤Flutter密碼
- Android自定義控制元件 帶文字提示的SeekBarAndroid控制元件
- 關於AS(Android studio)新增recyclerview控制元件後的進一步仿微信介面AndroidView控制元件
- 微信小程式獲取使用者頭像修改為圓形微信小程式
- 微信小程式swiper修改自定義指示點樣式微信小程式
- 微信JSSDK遇見的坑--vue微信自定義分享JSVue
- Android自定義View–仿QQ音樂歌詞AndroidView
- WPF滑塊控制元件(Slider)的自定義樣式控制元件IDE
- 微信小程式 自定義tabbar微信小程式tabBar
- 微信小程式自定義tabBar微信小程式tabBar
- QQ群頭像 微信群頭像 多圖合併框架實現框架
- Android 控制元件架構與自定義控制元件詳解Android控制元件架構
- 揭祕微信直銷高仿包包
- 東莞高仿包批發微信
- 微信頭像連結轉 base64
- 微信頭像生成圓形邀請卡
- Android自定義View--仿QQ音樂歌詞AndroidView
- Android自定義View--翻書控制元件(一)AndroidView控制元件
- Android自定義控制元件(神級)+MediaRecoder錄音Android控制元件