标签: ImageView

[Android] ImageView.ScaleType设置图解

ImageView的Scaletype决定了图片在View上显示时的样子,如进行何种比例的缩放,及显示图片的整体还是部分,等等。

设置的方式包括:

1. 在layout xml中定义android:scaleType=”CENTER”

2. 或在代码中调用imageView.setScaleType(ImageView.ScaleType.CENTER);

接下来,将对ScaleType的值和对应的显示效果用*直观的方式——真图演示的方法,来进行说明。

首先,是测试使用的原始图片:

%title插图%num(Dimensions: 128 * 128)

%title插图%num (Dimensions: 640 * 428)

好,开始下面的测试:

1. SetScaleType(ImageView.ScaleType.CENTER);

按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示

%title插图%num               %title插图%num

2. SetScaleType(ImageView.ScaleType.CENTER_CROP);

按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)

%title插图%num               %title插图%num

3. setScaleType(ImageView.ScaleType.CENTER_INSIDE);

将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽

%title插图%num               %title插图%num

4. setScaleType(ImageView.ScaleType.FIT_CENTER);

把图片按比例扩大/缩小到View的宽度,居中显示

%title插图%num               %title插图%num

5. FIT_START, FIT_END在图片缩放效果上与FIT_CENTER一样,只是显示的位置不同,FIT_START是置于顶部,FIT_CENTER居中,FIT_END置于底部。

在此就不给出示例了。

6. FIT_XY

不按比例缩放图片,目标是把图片塞满整个View。

%title插图%num               %title插图%num

ImageView的scaleType的属性理解

ImageView的scaleType的属性值有MATRIX,FIT_XY,FIT_START,FIT_END,FIT_CENTER,CENTER,CENTER_CROP,CENTER_INSIDE.

1.android:scaleType=“center”
保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size时,多出来的部分被截掉。
2.android:scaleType=“center_inside”
以原图正常显示为目的,如果原图大小大于ImageView的size,就按照比例缩小原图的宽高,居中显示在ImageView中。如果原图size小于ImageView的size,则不做处理居中显示图片。
3.android:scaleType=“center_crop”
以原图填满ImageView为目的,如果原图size大于ImageView的size,则与center_inside一样,按比例缩小,居中显示在ImageView上。如果原图size小于ImageView的size,则按比例拉升原图的宽和高,填充ImageView居中显示。
4.android:scaleType=“matrix”
不改变原图的大小,从ImageView的左上角开始绘制,超出部分做剪切处理。
5.androd:scaleType=“fit_xy”
把图片按照指定的大小在ImageView中显示,拉伸显示图片,不保持原比例,填满ImageView.
6.android:scaleType=“fit_start”
把原图按照比例放大缩小到ImageView的高度,显示在ImageView的start(前部/上部)。
7.android:sacleType=“fit_center”
把原图按照比例放大缩小到ImageView的高度,显示在ImageView的center(中部/居中显示)。
8.android:scaleType=“fit_end”
把原图按照比例放大缩小到ImageView的高度,显示在ImageVIew的end(后部/尾部/底部)

这里写图片描述

 

Android studio实现调用相机实现拍照、录像、读取相册并展示在新的页面的功能

本篇文章代码实现的功能是调用手机自带的相机进行拍照、录像和读取相册的功能,并将照片或者视频在指定的页面展示的功能。
我的效果是*页的布局会有拍照、录像、读取相册以及展示视频的选项。选择拍照或读取相册后,跳转到新的Intent进行操作并展示在ImageView中;选择录像会打开摄像头开始录像并保存在手机sd卡某一确定的缓存路径下;选择展示视频会展示这一确定的缓存路径下的视频文件。
下面是代码部分:
首先要对AndroidMainifest.xml进行设置,主要是存储权限的获得

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.mapscanner.mapscanner”>

<uses-feature
android:name=”android.hardware.camera2″
android:required=”true” />
<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />

<uses-permission android:name=”android.permission.FLASHLIGHT” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE” />
<uses-permission android:name=”android.permission.INTERNET” />

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<action android:name=”android.media.action.IMAGE_CAPTURE” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<provider
android:name=”android.support.v4.content.FileProvider”
android:authorities=”com.MapScanner.MapScanner”
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_paths” />
</provider>

<activity android:name=”.Second_Activity” />
<activity android:name=”.Image_album_showActivity” />
</application>

因为上面有一句android:resource=”@xml/file_paths”,是说在xml文件夹下定义了file_paths的值,所以我们 右键Resource new一个category取名为xml,会看到Resource下面出现了一个名为xml的文件夹,再右键文件夹选择new-> XML resource file,创建一个资源文件,并输入下面代码。

<?xml version=”1.0″ encoding=”utf-8″?>
<paths xmlns:android=”http://schemas.android.com/apk/res/android”>
<external-path name=”my_images” path=”” />
</paths>

下面首先看一下布局文件MainActivity.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:id=”@+id/take_photo”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Take Photos” />

<Button
android:id=”@+id/take_video”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Take videos” />

<Button
android:id=”@+id/choose_from_album”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Choose From Album” />

<Button
android:id=”@+id/show_videos”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Show previous video” />
</LinearLayout>

下面是MainActivity内的内容,主要实现摁键的跳转以及参数传递的功能

package com.mapscanner.mapscanner;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;

/*用来记录录像存储路径*/
File file = new File(Environment.getExternalStorageDirectory().getPath() + “/video.mp4”);//设置录像存储路径
Uri uri = Uri.fromFile(file);//文件转成Uri格式

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
Button takeVideo = (Button) findViewById(R.id.take_video);
Button showVideo = (Button) findViewById(R.id.show_videos);

//对照相功能的响应
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在新的Intent里面打开,并且传递TAKE_PHOTO选项
Intent intent = new Intent();
intent.setClass(MainActivity.this, Image_album_showActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);

Bundle bundle = new Bundle();
bundle.putInt(“id”, TAKE_PHOTO);//使用显式Intent传递参数,用以区分功能
intent.putExtras(bundle);

MainActivity.this.startActivity(intent);//启动新的Intent
}
});

//设置相册选择的响应
chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在新的Intent里面打开,并且传递CHOOSE_PHOTO选项
Intent intent = new Intent();
intent.setClass(MainActivity.this, Image_album_showActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);

Bundle bundle = new Bundle();
bundle.putInt(“id”, CHOOSE_PHOTO);
intent.putExtras(bundle);

MainActivity.this.startActivity(intent);
}
});
//设置录像选择的响应
takeVideo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 保存录像到指定的路径
// File file = new File(“storage/sdcard1/video.mp4” );//设置录像存储路径
try {
if (file.exists()) {
file.delete();
}
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 激活系统的照相机进行录像,通过Intent激活相机并实现录像功能
Intent intent = new Intent();
intent.setAction(“android.media.action.VIDEO_CAPTURE”);
intent.addCategory(“android.intent.category.DEFAULT”);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 0);
}
});
showVideo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

//initVideoPath();
Intent intent = new Intent();
intent.setClass(MainActivity.this, Second_Activity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
MainActivity.this.startActivity(intent);
setContentView(R.layout.activity_second_);
}
});
}
}

接着点击包名new一下,选择Activity->Empty Activity生成一组配套的布局和类的文件,取名为image_album_show实现对拍照和读取相册的响应。
下面是对拍照和读取相册进行响应的布局文件Activity_image_album_show.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.Image_album_showActivity”>

<Button
android:id=”@+id/Return_Back_to_page1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Return to previous page” />

<ImageView
android:id=”@+id/V_Image”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
/>
</LinearLayout>

下面是与Activity_image_album_show.xml配套的image_album_showActivity文件

package com.mapscanner.mapscanner;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;

public class Image_album_showActivity extends AppCompatActivity {
private ImageView picture;
private Uri imageUri;
private Button Return_page;
public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;

//接受前一个Intent传入的id
private Bundle bundle;
private int Show_Choice;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_album_show);
picture = (ImageView) findViewById(R.id.V_Image);
Return_page=(Button)findViewById(R.id.Return_Back_to_page1);
bundle = this.getIntent().getExtras();
Show_Choice=bundle.getInt(“id”);

Return_page.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent= new Intent();
intent.setClass(Image_album_showActivity.this, MainActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
startActivity(intent);
}
});
//接收Intent传递的id值,并判断,照相功能为1,打开相册功能为2
switch (Show_Choice)
{
//如果传递为TAKE_PHOTO
case TAKE_PHOTO:{
File outputImage = new File(getExternalCacheDir(), “output_image.jpg”);
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//判断版本号
if (Build.VERSION.SDK_INT < 24) {
imageUri = Uri.fromFile(outputImage);
} else {
imageUri = FileProvider.getUriForFile(Image_album_showActivity.this, “com.MapScanner.MapScanner”, outputImage);
}
// 启动相机程序
Intent intent = new Intent(“android.media.action.IMAGE_CAPTURE”);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);

try {// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
//如果传递为CHOOSE_PHOTO
case CHOOSE_PHOTO:
{
//如果没有权限则申请权限
if (ContextCompat.checkSelfPermission(Image_album_showActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Image_album_showActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
//调用打开相册
openAlbum();
}
default:
break;
}
}
private void openAlbum() {
Intent intent = new Intent(“android.intent.action.GET_CONTENT”);
intent.setType(“image/*”);
startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
}
else {
Toast.makeText(this, “You denied the permission”, Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (Show_Choice) {
case 1:
try {// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
break;

case 2:

// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
}
else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
break;
default:
break;
}
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
Log.d(“TAG”, “handleImageOnKitKat: uri is ” + uri);

if (DocumentsContract.isDocumentUri(this, uri)) {
// 如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if(“com.android.providers.media.documents”.equals(uri.getAuthority())) {
String id = docId.split(“:”)[1]; // 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + “=” + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
}
else if (“com.android.providers.downloads.documents”.equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse(“content://downloads/public_downloads”), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null);
}
}
else if (“content”.equalsIgnoreCase(uri.getScheme())) {
// 如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(uri, null);
}
else if (“file”.equalsIgnoreCase(uri.getScheme())) {
// 如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); // 根据图片路径显示图片
}

private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}

private String getImagePath(Uri uri, String selection) {
String path = null;
// 通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}

private void displayImage(String imagePath) {
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
}
else {
Toast.makeText(this, “failed to get image”, Toast.LENGTH_SHORT).show();
}
}
}

下面再新建一个Activity->EmptyActivity用于对视频的展示,我取名为Second_Activity,设置了一个button用于返回前一个activity以及用于视频展示的videoview控件。
还是先看一下布局文件activit_second_.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.Second_Activity”>

<Button
android:id=”@+id/Return_Back”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Return to previous page” />

<VideoView
android:id=”@+id/V_video”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
/>
</LinearLayout>

下面是Second_Activity中的内容

package com.mapscanner.mapscanner;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;

public class Second_Activity extends AppCompatActivity {
private VideoView mVideo;
private Button Turn_back;

File file = new File(Environment.getExternalStorageDirectory().getPath()+”/video.mp4″ );//设置录像存储路径
Uri uri = Uri.fromFile(file);//文件转成Uri格式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_);
Turn_back=(Button)findViewById(R.id.Return_Back);
mVideo=(VideoView)findViewById(R.id.V_video);
initVideoPath();
Turn_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent= new Intent();
intent.setClass(Second_Activity.this, MainActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
startActivity(intent);
}
});

}
private void initVideoPath() {
if (ContextCompat.checkSelfPermission(Second_Activity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Second_Activity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
Toast.makeText(this, “have found video”, Toast.LENGTH_SHORT).show();
mVideo.setVideoURI(uri);
mVideo.setVideoPath(file.getAbsolutePath());
MediaController mediaController=new MediaController(this);
mVideo.setMediaController(mediaController);
mediaController.setMediaPlayer(mVideo);
mVideo.requestFocus();
mVideo.start();
}
}

以上代码主要借鉴与《*行代码》以及网上的一些实例,在理解后进行了综合的应用,解决了我需要的功能,值得一提的是,本实例调用的相机是利用Intent对本机的相机进行了捕获从而实现媒体设备的使用功能,还有一种实现方法是调用camera库实现此功能。我在其它文章会介绍camera的使用以及如何通过Socket实现实时视频的传输。

*后附上自己总结的一些经验,因为我都是用真机测试程序,有时会出现程序打不开闪退的现象,总结后主要有:
1、可能未获得权限,比如使用媒体设备,比如读取内存都需要获得权限,如果未获得权限就会闪退。
2、当跳转到一个新的页面时,都会有setContentView(R.layout.xxxxx_);
而对于button等的响应需要用到xxx=(Button)findViewById(R.id.xxx); findViewById必须放在setContentView后面,否则会找不到此按钮而闪退。

Android开发技巧-定制仿微信图片裁剪控件

拍照——裁剪,或者是选择图片——裁剪,是我们设置头像或上传图片时经常需要的一组操作。上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现。

背景
下面的需求都来自产品。
裁剪图片要像微信那样,拖动和放大的是图片,裁剪框不动。
裁剪框外的内容要有半透明黑色遮罩。
裁剪框下面要显示一行提示文字(这点我至今还是持保留意见的)。
在Android中,裁剪图片的控件库还是挺多的,特别是github上比较流行的几个,都已经进化到比较稳定的阶段,但比较遗憾的是它们的裁剪过程是拖动或缩放裁剪框,于是只好自己再找,看有没有现成或半成品的轮子,可以不必从零开始。
踏破铁鞋无觅处,*天不负苦心人。我终于找到了两篇博客:《Android高仿微信头像裁剪》和《Android 高仿微信头像截取 打造不一样的自定义控件》,以及csdn上找到的前面博客所对应的一份代码,并*终实现了自己的裁剪控件。

大神的实现过程
首先先了解一下上面的高仿微信裁剪控件的实现过程。说起来也不难,主要是下面几点:
1,重写ImageView,并监听手势事件,包括双点,两点缩放,拖动,使它成为一个实现缩放拖动图片功能的控件。
2,定义一个Matrix成员变量,对于维护该图片的缩放、平移等矩阵数据。
3,拖动或缩放时,图片与裁剪框的相交面积一定与裁剪框相等。即图片不能拖离裁剪框。
3,在设置图片时,先根据图片的大小进行初始化的缩放平移操作,使得上面第三条的条件下图片尽可能的小。
4,每次接收到相对应的手势事件,都进行对应的矩阵计算,并将计算结果通过ImageView的setImageMatrix方法应用到图片上。
5,裁剪框是一个单独的控件,与ImageView同样大,叠加到它上面显示出来。
6,用一个XXXLayout把裁剪框和缩放封装起来。
7,裁剪时,先创建一个空的Bitmap并用其创建一个Canvas,把缩放平移后的图片画到这个Bitmap上,并创建在裁剪框内的Bitmap(通过调用Bitmap.createBitmap方法)。

我的定制内容
我拿到的代码是鸿洋大神版本之后再被改动的,代码上有点乱(虽然功能上是实现的裁剪)。在原有的功能上,我希望进行的改动有:

合并裁剪框的内容到ImageView中
裁剪框可以是任意长宽比的矩形
裁剪框的左右外边距可以设置
遮罩层颜色可以设置
裁剪框下有提示文字(自己的产品需求)
后面产品又加入了一条裁剪图片的*大大小
属性定义
在上面的功能需求中,我定义了以下属性:

<declare-styleable name=”ClipImageView”>
<attr name=”civHeight” format=”integer”/>
<attr name=”civWidth” format=”integer”/>
<attr name=”civTipText” format=”string”/>
<attr name=”civTipTextSize” format=”dimension”/>
<attr name=”civMaskColor” format=”color”/>
<attr name=”civClipPadding” format=”dimension”/>
</declare-styleable>

其中:

civHeight和civWidth是裁剪框的宽高比例。
civTipText提示文字的内容
civTipTextSize提示文字的大小
civMaskColor遮罩层的颜色值
civClipPadding裁剪内边距。由于裁剪框是在控件内部的,*终我选择使用padding来说明裁剪框与我们控件边缘的距离。
成员变量
成员变量我进行了一些改动,把原本用于定义裁剪框的水平边距变量及其他没什么用的变量等给去掉了,并加入了自己的一些成员变量,*终如下:

private final int mMaskColor;//遮罩层颜色

private final Paint mPaint;//画笔
private final int mWidth;//裁剪框宽的大小(从属性上读到的整型值)
private final int mHeight;//裁剪框高的大小(同上)
private final String mTipText;//提示文字
private final int mClipPadding;//裁剪框相对于控件的内边距

private float mScaleMax = 4.0f;//图片*大缩放大小
private float mScaleMin = 2.0f;//图片*小缩放大小

/**
* 初始化时的缩放比例
*/
private float mInitScale = 1.0f;

/**
* 用于存放矩阵
*/
private final float[] mMatrixValues = new float[9];

/**
* 缩放的手势检查
*/
private ScaleGestureDetector mScaleGestureDetector = null;
private final Matrix mScaleMatrix = new Matrix();

/**
* 用于双击
*/
private GestureDetector mGestureDetector;
private boolean isAutoScale;

private float mLastX;
private float mLastY;

private boolean isCanDrag;
private int lastPointerCount;

private Rect mClipBorder = new Rect();//裁剪框
private int mMaxOutputWidth = 0;//裁剪后的图片的*大输出宽度

构造方法
构造方法里主要是多了一些我们自定义属性的读取:

public ClipImageView(Context context) {
this(context, null);
}

public ClipImageView(Context context, AttributeSet attrs) {
super(context, attrs);

setScaleType(ScaleType.MATRIX);
mGestureDetector = new GestureDetector(context,
new SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (isAutoScale)
return true;

float x = e.getX();
float y = e.getY();
if (getScale() < mScaleMin) {
ClipImageView.this.postDelayed(new AutoScaleRunnable(mScaleMin, x, y), 16);
} else {
ClipImageView.this.postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16);
}
isAutoScale = true;

return true;
}
});
mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(this);

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClipImageView);
mWidth = ta.getInteger(R.styleable.ClipImageView_civWidth, 1);
mHeight = ta.getInteger(R.styleable.ClipImageView_civHeight, 1);
mClipPadding = ta.getDimensionPixelSize(R.styleable.ClipImageView_civClipPadding, 0);
mTipText = ta.getString(R.styleable.ClipImageView_civTipText);
mMaskColor = ta.getColor(R.styleable.ClipImageView_civMaskColor, 0xB2000000);
final int textSize = ta.getDimensionPixelSize(R.styleable.ClipImageView_civTipTextSize, 24);
mPaint.setTextSize(textSize);
ta.recycle();

mPaint.setDither(true);
}

定义裁剪框
裁剪框的位置
裁剪框是在控件正中间的,首先我们从属性中读取到的是宽高的比例,以及左右边距,但是在构造方法中,由于控件还没有绘制出来,无法获取到控件的宽高,所以并不能计算裁剪框的大小和位置。所以我重写了onLayout方法,在这里计算裁剪框的位置:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
final int width = getWidth();
final int height = getHeight();
mClipBorder.left = mClipPadding;
mClipBorder.right = width – mClipPadding;
final int borderHeight = mClipBorder.width() * mHeight / mWidth;
mClipBorder.top = (height – borderHeight) / 2;
mClipBorder.bottom = mClipBorder.top + borderHeight;
}

绘制裁剪框
这里我顺便把绘制提示文字的代码也一并给出,都是在同一个方法里的。很简单,重写onDraw方法即可。绘制裁剪框有两种方法,一是绘制一个满屏的遮罩层,然后从中间抠出一个长方形出来,但是我用的时候发现抠不出来,所以我采用的是下面这一种:

%title插图%num

先画上下两个矩形,再画左右两个矩形,中间所围起来的没有画的部分就是我们的裁剪框。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int width = getWidth();
final int height = getHeight();

mPaint.setColor(mMaskColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(0, 0, width, mClipBorder.top, mPaint);
canvas.drawRect(0, mClipBorder.bottom, width, height, mPaint);
canvas.drawRect(0, mClipBorder.top, mClipBorder.left, mClipBorder.bottom, mPaint);
canvas.drawRect(mClipBorder.right, mClipBorder.top, width, mClipBorder.bottom, mPaint);

mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(1);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(mClipBorder.left, mClipBorder.top, mClipBorder.right, mClipBorder.bottom, mPaint);

if (mTipText != null) {
final float textWidth = mPaint.measureText(mTipText);
final float startX = (width – textWidth) / 2;
final Paint.FontMetrics fm = mPaint.getFontMetrics();
final float startY = mClipBorder.bottom + mClipBorder.top / 2 – (fm.descent – fm.ascent) / 2;
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText(mTipText, startX, startY, mPaint);
}
}

修改图片的初始显示
这里我不使用全局布局的监听(通过getViewTreeObserver加入回调),而是直接重写几个设置图片的方法,在设置图片后进行初始显示的设置:

@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
postResetImageMatrix();
}

@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
postResetImageMatrix();
}

@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
postResetImageMatrix();
}

private void postResetImageMatrix() {
post(new Runnable() {
@Override
public void run() {
resetImageMatrix();
}
});
}

resetImageMatrix()方法设置图片的初始缩放及平移,参考图片大小,控件本身大小,以及裁剪框的大小进行计算:

/**
* 垂直方向与View的边矩
*/
public void resetImageMatrix() {
final Drawable d = getDrawable();
if (d == null) {
return;
}

final int dWidth = d.getIntrinsicWidth();
final int dHeight = d.getIntrinsicHeight();

final int cWidth = mClipBorder.width();
final int cHeight = mClipBorder.height();

final int vWidth = getWidth();
final int vHeight = getHeight();

final float scale;
final float dx;
final float dy;

if (dWidth * cHeight > cWidth * dHeight) {
scale = cHeight / (float) dHeight;
} else {
scale = cWidth / (float) dWidth;
}

dx = (vWidth – dWidth * scale) * 0.5f;
dy = (vHeight – dHeight * scale) * 0.5f;

mScaleMatrix.setScale(scale, scale);
mScaleMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

setImageMatrix(mScaleMatrix);

mInitScale = scale;
mScaleMin = mInitScale * 2;
mScaleMax = mInitScale * 4;
}

注意:这里有一个坑。把一个Bitmap设置到ImageView中,显示时要计算的是ImageView获取的Drawable对象以及这个对象的宽高,而不是Bitmap对象。Drawable对象可能由于对Bitmap的放大或缩小显示,导致它的宽或高与Bitmap的宽高不同。
还有一点小注意:获取控件宽高是要在控件被绘制出来之后才能获取得到的,所以上面我通过post一个Runnable对象到主线程的Looper中,保证它是在界面绘制完成之后被调用。

缩放及拖动
缩放及拖动时都需求判断是否超出边界,如果超出,则取允许的*终值。这里的代码我没怎么动,稍后可直接参考源码,暂不赘述。

裁剪
这里是另外一个改造的重点了。
首先,鸿洋大神是通过创建一个空的Bitmap,并根据它创建出一个Canvas对象,然后通过draw方法把缩放后的图片给绘制到这个Bitmap中,再调用Bitmap.createBitmap得到属于裁剪框的内容。但是我们已经重写了onDraw方法画出裁剪框,所以这里就不考虑了。
另外,这种方法还有一个问题:它绘制的是Drawable对象。如果我们设置进去的是一个比较大的Bitmap,那么就可能被缩放了,这里裁剪的是缩放后的Bitmap,也就是它不是对原图进行裁剪的。
这里我参考了其他裁剪图片库,通过保存了缩放平移的Matrix成员变量进行计算,获取出裁剪框在其的对应范围,并根据*终所需(我们产品要限制一个*大大小),得到*终的图片,代码如下:

public Bitmap clip() {
final Drawable drawable = getDrawable();
final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();

final float[] matrixValues = new float[9];
mScaleMatrix.getValues(matrixValues);
final float scale = matrixValues[Matrix.MSCALE_X] * drawable.getIntrinsicWidth() / originalBitmap.getWidth();
final float transX = matrixValues[Matrix.MTRANS_X];
final float transY = matrixValues[Matrix.MTRANS_Y];

final float cropX = (-transX + mClipBorder.left) / scale;
final float cropY = (-transY + mClipBorder.top) / scale;
final float cropWidth = mClipBorder.width() / scale;
final float cropHeight = mClipBorder.height() / scale;

Matrix outputMatrix = null;
if (mMaxOutputWidth > 0 && cropWidth > mMaxOutputWidth) {
final float outputScale = mMaxOutputWidth / cropWidth;
outputMatrix = new Matrix();
outputMatrix.setScale(outputScale, outputScale);
}

return Bitmap.createBitmap(originalBitmap,
(int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight,
outputMatrix, false);
}

由于我们是对Bitmap进行裁剪,所以首先获取这个Bitmap:

final Drawable drawable = getDrawable();
final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();

然后,我们的矩阵值可以通过一个包含9个元素的float数组读出:

final float[] matrixValues = new float[9];
mScaleMatrix.getValues(matrixValues);

比如,读X上的缩放值,代码为matrixValues[Matrix.MSCALE_X]。
要特别注意一点,在前文也有提到,这里缩放的是Drawable对象,但是我们裁剪时用的Bitmap,如果图片太大的话是可能在Drawable上进行缩放的,所以缩放大小的计算应该为:

final float scale = matrixValues[Matrix.MSCALE_X] * drawable.getIntrinsicWidth() / originalBitmap.getWidth();

然后获取图片平移量:

final float transX = matrixValues[Matrix.MTRANS_X];
final float transY = matrixValues[Matrix.MTRANS_Y];

计算裁剪框对应在图片上的起点及宽高:

final float cropX = (-transX + mClipBorder.left) / scale;
final float cropY = (-transY + mClipBorder.top) / scale;
final float cropWidth = mClipBorder.width() / scale;
final float cropHeight = mClipBorder.height() / scale;

上面就是我们所要裁剪出来的*终结果。
但是,我前面也说的,应产品需求,要限制*大输出大小。由于我们裁剪出来的图片宽高比是3:2,我这里只取宽度(你要取高度也可以)进行限制,所以又加上了如下代码,当裁剪出来的宽度超出我们*大宽度时,进行缩放。

Matrix outputMatrix = null;
if (mMaxOutputWidth > 0 && cropWidth > mMaxOutputWidth) {
final float outputScale = mMaxOutputWidth / cropWidth;
outputMatrix = new Matrix();
outputMatrix.setScale(outputScale, outputScale);
}

*终根据上面计算出来的值,创建裁剪出来的Bitmap:

Bitmap.createBitmap(originalBitmap,
(int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight,
outputMatrix, false);

这样,图片裁剪控件就算全部完成。

实现效果

%title插图%num %title插图%num

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速