}
}
我们直接预设了一个水平方向的边距,根据边距计算出正方形的边长,接下来就是按照上图分别会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=“裁切”/>
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
最后这里放上我这段时间复习的资料,这个资料也是偶然一位朋友分享给我的,里面包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
还有 高级架构技术进阶脑图、高级进阶架构资料 帮助大家学习提升进阶,也可以分享给身边好友一起学习。
一起互勉~
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
[外链图片转存中…(img-r6A2Qwzv-1712605351638)]
[外链图片转存中…(img-ARaSmPab-1712605351638)]
[外链图片转存中…(img-kl8LIpcI-1712605351638)]
[外链图片转存中…(img-GA6Jjvu8-1712605351639)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
最后这里放上我这段时间复习的资料,这个资料也是偶然一位朋友分享给我的,里面包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
还有 高级架构技术进阶脑图、高级进阶架构资料 帮助大家学习提升进阶,也可以分享给身边好友一起学习。
[外链图片转存中…(img-JoKZJnqR-1712605351639)]
[外链图片转存中…(img-1IUjWZJJ-1712605351639)]
一起互勉~
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!