Android 高仿微信头像截取 打造不一样的自定义控件

/**

  • 边框的颜色,默认为白色

*/

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” />

效果图:

故意放了个背景,没撒用,就是为了能看出效果,可以看到我们的框框绘制的还是蛮不错的~~嗯,这个框框距离屏幕左右两侧的距离应该抽取出来,嗯,后面再说~

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” />

然后这两个类中都有一个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” />

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

<item

android:id=“@+id/id_action_clip”

android:icon=“@drawable/actionbar_clip_icon”

android:showAsAction=“always|withText”

android:title=“裁切”/>

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值