您的位置:首页 > 移动开发

自制app(游戏)Please Go之上传头像篇----萌新成长之路

2017-03-17 15:02 357 查看

自制app(游戏)Please Go之上传头像篇

最近在自己开发一个自己想出来的游戏(大家可以猜猜是一款什么游戏~),想一边学习一边巩固,所以在这里把项目里面用到的功能分模块写在博客里面。希望有些地方能帮到大家,并且特别特别希望能得到大神的指导~~

(有些点由于篇幅原因没有写在注释和步骤中,在最后的TIPS里面会针对一些方法进行解释,然后还有本菜鸟的几个问题,大家一定要看呦~)。

(1)xml

很简单一个LinearLayout 里面就一个按钮和一张图片

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context="com.example.choosephoto.MainActivity">

<Button
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="choosePicture"
android:text="选择头像"/>

<ImageView
android:id="@+id/headPicture"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>




(2)java代码

步骤1:布局

绑定了ImageView ,初始化文件的存储位置为sd卡根目录。然后写了按钮的响应方法 choosePicture

需要加如下权限用来删除文件。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


package com.example.choosephoto;

import android.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

private ImageView headPicture;
private Uri imageUri;//图片存储的路径
private File file;//需要存储的图片文件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
headPicture = (ImageView) findViewById(R.id.headPicture);
file = new File(Environment.getExternalStorageDirectory(), "headPicture.jpg");//新建一个文件(路径,文件名称)
//Environment.getExternalStorageDirectory()为获取sd的根目录。(双sd卡获取外置sd卡,没有外置sd卡,则获取内部sd卡)
imageUri = Uri.fromFile(file);
}

public void choosePicture(View view)
{
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}


步骤2:Dialog

在choosePicture方法中 写一个AlertDialog 用于让用户选择使用哪种方式上传头像(拍照或者从本地图库里选择)。这里写一个string数组 然后调用AlertDialog.Builder().setItems()方法设置可选项。onClick里面的int i就是数组编号。

public void choosePicture(View view)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择您的头像方式");
String[] string = {"拍照","从相册里选择"};
builder.setItems(string, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i)
{
case 0:
//拍照
break;
case 1:
//从相册里选择
break;
}
}
});
builder.show();
}


步骤3(1):拍照

1、新创建一个文件表示图片,然后判断其是否存在,若存在则删除。把图片的路径赋给全局变量imageUri。

2、定义一个intent调用系统的摄像头进行拍照, 然后用startActivityForResult来开始intent。

3、需要一个权限来读取手机的文件
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


4000
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) {
case 0:
//拍照
Intent openCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//打开系统照相机
openCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//表示需要在imageUri这个位置存放东西
startActivityForResult(openCamera, 0);
break;
case 1:
//从相册里选择
break;
}
}


在onActivityResult里面接收照完照片以后,利用requestCode来判断我收到的是哪个Intent的回调。这里我们在前面指定标志位为0,则我们在0下面进行本地照片的读。使用流的形式把在imageUri“位置“下的照片通过流的形式获取bitmap对象,然后设置我们的headpicture就大功告成啦!!

@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data) {
switch (requestCode) {
case 0:
//拍照
if (resultCode == RESULT_OK) {
try {
//根据imageUri用getContentResolver来获取流对象 再转化成bitmap
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver()
.openInputStream(imageUri));
if (bitmap==null)
{
//判断bitmap是否为空
Toast.makeText(this,"图像没有存储到sd卡根目录",Toast.LENGTH_SHORT)
.show();
}
headPicture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {

}
}
break;
case 1:
//从相册获取
break;
}
}


步骤3(2):从相册获取

1、写一个Intent,MediaStore.Images.Media.INTERNAL_CONTENT_URI表示查看手机中外置存储的图片,而前一个参数Intent.ACTION_PICK表示从列表里面挑选一个项目返回。

2、同样还是跟拍照一样调用startActivityForResult启动Intent,这里用1表示从相册选择照片的标志位。

builder.setItems(string, new DialogInterface.OnClickListener() {//(实现了charsequence的类数组这里用string数组,一个点击事件的监听器)

。。。。。。
case 1:
//从相册里选择
Intent chooseInAlbum= new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
startActivityForResult(chooseInAlbum, 1);
break;
}


代码很简单,然后后面接受照片的代码也跟上面拍照的一样。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
。。。。。。
case 1:
//从相册获取
if (resultCode == RESULT_OK) {
try {
//根据imageUri用getContentResolver来获取流对象 再转化成bitmap
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
if (bitmap==null)
{
//判断bitmap是否为空
Toast.makeText(this,"图像没有存储到sd卡根目录",Toast.LENGTH_SHORT).show();
}
headPicture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {

}
}
break;
}


步骤4:图片裁剪

做到这里 我们最基本的功能已经实现了,我们现在再考虑一下这种情况:如果用户手机的照相机像素比较高或者用手机里面像素很高的图片拿来当做头像,那一张图片差不多会占到大约1~4M,考虑到用服务器来管理用户数据,头像在设置以后需要上传服务器备份,那么会有以下两个问题:1、用户上传的流量消耗大,若网速低则上传过程长导致用户体验变差;2、服务器需要存储过多数据,且若不能及时关闭连接则会导致服务器负担过高,限制之后的用户访问。

我们在这里的解决方法是简单的把选择的图片进行裁剪以后再保存在根目录和显示在app里面,类似于QQ头像。

我们写一个cropPicture()方法用来裁剪图片。

private void cropPicture(Uri uri)
{
//新建一个表示裁剪的Intent
Intent intent = new Intent("com.android.camera.action.CROP");
//表明我要裁剪的目标是uri这个地址,文件类型是图片
intent.setDataAndType(uri,"image/*");
//指定长宽的比例为1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//指定宽高为1000
intent.putExtra("outputX", 1000);
intent.putExtra("outputY", 1000);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent,2);
}


我们之前的代码也需要更改,流程为从拍照/从相册获取图片以后,调用cropPicture()裁剪图片。以下是全部代码

package com.example.choosephoto;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {

private ImageView headPicture;
private Uri imageUri;//图片存储的路径
private File file;//需要存储的图片文件

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
headPicture = (ImageView) findViewById(R.id.headPicture);
file = new File(Environment.getExternalStorageDirectory(), "headPicture.jpg");//新建一个文件(路径,文件名称)
//Environment.getExternalStorageDirectory()为获取sd的根目录。
imageUri = Uri.fromFile(file);
}

public void choosePicture(View view) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择您的头像方式");
String[] string = {"拍照", "从相册里选择"};
builder.setItems(string, new DialogInterface.OnClickListener() {//(实现了charsequence的类数组这里用string数组,一个点击事件的监听器)
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) {
case 0:
//拍照
Intent openCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//打开系统照相机
openCamera.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(openCamera,0);
break;
case 1:
//从相册里选择
Intent chooseInAlbum = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(chooseInAlbum,1);
break;
}
}
});
builder.show();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 0:
//拍照
if (resultCode==RESULT_OK) {
cropPicture(imageUri);
}
break;
case 1:
//从相册获取
if (resultCode==RESULT_OK) {
cropPicture(data.getData());
}
break;
case 2:
//裁剪之后
if (resultCode == RESULT_OK) {
setHeadPicture();
}
break;
}
}

private void setHeadPicture()
{
try {
//根据imageUri用getContentResolver来获取流对象 再转化成bitmap
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
if (bitmap==null)
{
//判断bitmap是否为空
Toast.makeText(this,"图像没有存储到sd卡根目录",Toast.LENGTH_SHORT).show();
}
headPicture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {

}
}

private void cropPicture(Uri uri)
{
//新建一个表示裁剪的Intent
Intent intent = new Intent("com.android.camera.action.CROP");
//表明我要裁剪的目标是uri这个地址,文件类型是图片
intent.setDataAndType(uri,"image/*");
//指定长宽的比例为1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//指定宽高为1000
intent.putExtra("return-data",false);
intent.putExtra("scale",true);
intent.putExtra("outputX", 1000);
intent.putExtra("outputY", 1000);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent,2);
}
}


大家看了以后会发现这里照相没有像从相册获取照片那样在原来的Intent基础上进行加工。这个是因为android的隐式Intent的使用问题,有些Intent可以加extras有些不可以,

TIPS

startActivityForResult方法的参数为(Intent,标志位)。为什么不用startActivity()?因为我们拍完照以后还要回到这个界面,所以拍完照以后需要返回一个结果到底有没有成功。而startActivityForResult恰好有这个功能。那么用什么方法来监听这个回调呢?有一个onActionResult()方法需要我们重写。requestCode表示前面提到的标志位,resultCode表示拍完照之后返回的结果位,若为RESULT_OK则表示成功。最后一个参数data,大家可以看到是Intent类型的。千万别以为这个是上面的openCamera,这个data是表示照完照片以后返回的意图。(大家在编写的时候有时候会发现data==null,关于这一点我就偷个懒引用一下其他博主的文章吧,说的挺详细的系统照相机返回Intent为null)。

Environment.getExternalStorageDirectory()为获取sd的根目录。为内置sd卡还是外置sd卡因手机而异。大家可以log出来看看。

android6.0以后获取权限与之前有很大不同,一些比较“危险”的权限需要向用户动态获取,不能只写在manifest里面。具体的大家可以网上搜一下,我之后也会发一篇关于动态获取权限的博客。

下一篇博客为把头像上传到服务器的okhttp写法,大家有兴趣可以看看~

本菜问题(路过的大神求解,万分感谢!!)

照相机拍出来的2K照片最大占用将近5M,但是从app里面输出来的同像素图片只有几百K。例如本例中,把outputX和outputY定为1500,图片大小也只有几百K。是系统自动压缩了吗?

把outputX和outputY定不了很大,我试了2K的X,Y发现实际输出来为1936*2582,不知道为什么,大家可以试试。

Intent里面调用系统uri的规则也不是很清楚,比如为什么在调用照相机的时候加一句intent.putExtras(“crop”,”true”)会报异常。

本项目github地址

附录

本文里几个知识点相关的其他博主的博客,有钻研精神的童鞋们可以看看

http://blog.csdn.net/lo5sea/article/details/38308513 系统Uri相关

http://blog.csdn.net/playboyanta123/article/details/7913679 IntentAction相关

http://blog.csdn.net/yuzhiboyi/article/details/8645730 sd卡相关

https://my.oschina.net/ryanhoo/blog/86843 MediaStore裁剪

http://www.cnblogs.com/w-y-f/p/4028379.html MediaStore裁剪
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息