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 ~~

源码点击下载

---------------------------------------------------------------------------------------------------------

我建了一个QQ群,方便大家交流。群号:55032675

时间: 2024-10-27 05:50:38

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

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

转载请表明出处:http://blog.csdn.net/lmj623565791/article/details/39761281,本文出自:[张鸿洋的博客] 1.概述 前面已经写了关于检測手势识别的文章.假设不了解能够參考:Android 手势检測实战 打造支持缩放平移的图片预览效果(下).首先本篇文章,将对之前博客的ZoomImageView代码进行些许的改动与改善,然后用到我们的本篇博客中去,实现仿微信的头像截取功能.当然了.个人觉得微信的截取头像功能貌似做得不太好.本篇博客准备去其糟粕

Android高仿微信头像裁剪

最近公司的APP很多用户反应无法上传头像,于是打算修改原来头像裁剪的代码.参考微信.QQ.唱吧头像裁剪的操作,决定就仿微信头像裁剪来上传用户头像,在Android大神鸿洋的一篇高仿微信头像的博客(博客地址结尾会贴出来)的基础上加了一些代码,加的代码主要增加如下的功能: 1.增加对大图的处理,缩放到我们裁剪框的大小. 2.裁剪后的图片保存到临时文件里,把临时文件的路径返回到需要处理的界面,因为在三星S4传byte数组返回数据时会闪退,传路径则正常. 3.对有些系统返回旋转过的图片进行处理. 这个功

高仿QQ头像截取升级版

观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图:   为了适应大家不同需求,这次打了两个包,及上图中一个方形的头像截取demo和一个圆形的: 原理: 方形: 如图:底层即图片层,在上层的画布中,先将裁剪区四周根据裁剪区大小画上阴影,然后在画上裁剪区的白色边框(空心):如下图 主要代码如下: @Override protected void onDraw(Canvas canvas) { supe

Android 高仿微信实时聊天 基于百度云推送

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天终于有幸利用百度云推送仿一仿微信聊天了~~~ 首先特别感谢:weidi1989分享的Android之基于百度云推送IM ,大家可以直接下载:省了很多事哈,本例中也使用了weidi的部分代码,凡是@author way的就是weidi1989的代码~~ 1.效果图 核心功能也就上面的两张图了~~~我拿着手机和模拟器

Android 高仿微信6.0主界面 带你玩转切换图标变色

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41087219,本文出自:[张鸿洋的博客] 1.概述 学习Android少不了模仿各种app的界面,自从微信6.0问世以后,就觉得微信切换时那个变色的Tab图标屌屌的,今天我就带大家自定义控件,带你变色变得飞起~~ 好了,下面先看下效果图: 清晰度不太好,大家凑合看~~有木有觉得这个颜色弱爆了了的,,,下面我动动手指给你换个颜色: 有没有这个颜色比较妖一点~~~好了~下面开始介绍

android高仿微信拍照、多选、预览、删除(去除相片)相册功能

先声明授人与鱼不如授人与渔,只能提供一个思路,当然需要源码的同学可以私下有偿问我要源码:QQ:508181017 工作了将近三年时间了,一直没正儿八经的研究系统自带的相册和拍照,这回来个高仿微信的拍照.多选.预览.删除(去除相片)相册功能,之前开发的所有应用都带有这需求,但是一直都不实用!废话就不多说了,先来捋一下思路: 1.拍照能实时保存到本地并实时查询(不必用广播或者服务) 2.拍照保存到到自定义路径并根据不同文件夹显示文件夹下的相片 3.多选规定张数图片 4.用到的集合有: (1).所有相

Android高仿微信图片选择上传工具

源码托管地址:https://github.com/SleepyzzZ/photo-selector 话不多说,先上效果图(高仿微信图片选择器): 图片选择界面: 图片预览界面: 批量上传图片: 实现的功能介绍: 1.图片异步加载,使用Glide开源库实现加载; 2.图片的预览界面,支持左右滑动,双击放大浏览; 3.图片批量上传,使用OkHttp来实现与Servlet服务器的通信; 使用方法(Android Studio): 新建工程,File->New->Import Module导入pho

高仿QQ头像截取

花费了半天时间,把 仿QQ头像截取的方法整理了下,并制作了一个demo以供大家参考,基本上实现了qq中我的资料界面上(包括背景透明化,上滑标题栏显示,下拉隐藏等)的大致效果,先上图看效果吧: 支持的功能: 1.选择裁剪的图片支持手势放大缩小(包括双击放大缩小): 2.判断图片边缘,即裁剪区域不会超过图片边缘: 原理: 一个重写的RelativeLayout,里面放了两层View,下层是重写的支持缩放的ImageView用于放置选择裁剪的图片,ImageView上层是一个自定义View,通过onD

Android 高仿微信图片选择器(瀑布流)

前言 在很多很多的项目中,都有选择本地图片的功能,现在就带大家做一个仿微信的图片选择器 1.和微信相比,由于博主是平板,微信在博主的平板中的图片是很模糊的,而我们的这个比微信的清晰,但是代价基本就是内存的多消耗,但是现在的收集基本上这点内存还是有的,图片也是经过压缩的 2.和鸿洋封装的相比,有些人可能会说和大神的有可比性么?我可以很直白的说这个图片选择器就是参考鸿洋大神以前封装的图片选择器,并且进行代码的分层.逻辑的重新梳理.优化显示效果.去除很多难懂的代码,用浅显易懂的代码实现之!,并且图片的