第一个Android应用《Exif编辑器》开发日记

开发背景

某日突然想在手机上修一下改照片的日期,但是搜了半天也没有找到合适的软件,现有的软件大都智能查看图片的信息,但无法修改,最终还是将图片传到了电脑上进行的操作。恰巧刚刚学到了RecyclerView的滚动视图,感觉可以解决这个问题,便准备开发一个exif编辑器。

最终效果

下载地址:https://www.coolapk.com/apk/257548
GitHub地址:https://github.com/jiahui6021/Exif_Edit
1、主页面
非常简单的布局,一行文字,一个按钮,和一个图片。

2、点击打开图片后调用图库选择图片并显示出图片和基本的exif信息。

3、点击对应的exif信息,弹出对话框进行修改

第一步:使程序能够打开图片

首先需要编写按钮的点击动作,由于需要使用系统图库选择图片,这里使用了隐式Intent。且使用startActivityForResult来启动,以便后面获取打开图片对应的Uri。

openpic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(intent,1);
            }
        });

接下来需要编写Intent将图片路径返回回来后的逻辑了。
重写onActivityResult方法,利用返回的Uri找到对应的图片路径并将其显示到屏幕上。

 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            case 1:
                if(resultCode==RESULT_OK){
                    try {
                        Uri selectedImage = data.getData();
                        String[] filePathColumn = {MediaStore.Images.Media.DATA};
                        Cursor cursor = getContentResolver().query(selectedImage,
                                filePathColumn, null, null, null);//从系统表中查询指定Uri对应的照片
                        cursor.moveToFirst();
                        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                        path = cursor.getString(columnIndex);  //获取照片路径
                        cursor.close();
                        Toast.makeText(MainActivity.this,"获取到图片路径:"+path,Toast.LENGTH_SHORT).show();
                        Bitmap bitmap = BitmapFactory.decodeFile(path);
                        img_show.setImageBitmap(bitmap);
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }
                }
                break;
        }
    }

但是这样还不够,因为我们并没有读取存储的权限,所以首先,我们要去AndroidManifest.xml中加入<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />更改后的文件如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pic">

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

    <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=".AboutActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

但是这样还不够,因为Android在6.0之后对权限管理进行了升级,对于读取存储空间这种敏感权限需要动态申请,所以我们在MainActivity.javaonCreate方法中对权限进行申请

//请求权限
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);
            }
        }

这样一来,程序就可以通过系统图库从手机的存储中打开一张图片并显示在界面上了。

第二步,使用Recyclerview显示图片的Exif信息

首先需要获取文件的Exif信息,因此先创建一个Msg.java的实体类
包含三个成员变量

private String title;
    private int type;
    private String msg;

title代表的是对应exif信息的中文描述,type代表的是对应信息的种类。msg代表的是对应的信息,并生成对应的构造方法与set,get方法。
再编写一个类来获取文件的exif信息

package com.example.pic;

import android.media.ExifInterface;
import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class GetExfi {
    List list;
    public List getExfi(String path){
        try{
            list = new ArrayList();
            ExifInterface exifInterface = new ExifInterface(path);
            String Orientation=exifInterface.getAttribute(ExifInterface.TAG_ORIENTATION);
            String DateTime=exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
            String Make=exifInterface.getAttribute(ExifInterface.TAG_MAKE);
            String Model=exifInterface.getAttribute(ExifInterface.TAG_MODEL);
            String Flash=exifInterface.getAttribute(ExifInterface.TAG_FLASH);
            String ImageWidth=exifInterface.getAttribute(ExifInterface.TAG_IMAGE_WIDTH);
            String ImageLength=exifInterface.getAttribute(ExifInterface.TAG_IMAGE_LENGTH);
            String ExposureTime=exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
            String FNumber=exifInterface.getAttribute(ExifInterface.TAG_APERTURE);
            String ISOSpeedRatings=exifInterface.getAttribute(ExifInterface.TAG_ISO);
            String GPSLatitude=exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
            String GPSLongitude=exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);

            list.add(new Msg("方向",0,Orientation));
            list.add(new Msg("日期",1,DateTime));
            list.add(new Msg("制造商",2,Make));
            list.add(new Msg("设备型号",3,Model));
            list.add(new Msg("闪光灯",4,Flash));
            list.add(new Msg("图像宽度",5,ImageWidth));
            list.add(new Msg("图像长度",6,ImageLength));
            list.add(new Msg("曝光时间",7,ExposureTime));
            list.add(new Msg("光圈值",8,FNumber));
            list.add(new Msg("感光度",9,ISOSpeedRatings));
            list.add(new Msg("经度",10,GPSLatitude));
            list.add(new Msg("维度",11,GPSLongitude));

        } catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }
}

然后就是如何让获取到的信息显示在屏幕上了。
使用recyclerview布局需要先在build.gradle文件中导入相关包implementation 'com.android.support:recyclerview-v7:28.0.0'
然后在布局文件中添加

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/msg_RecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </androidx.recyclerview.widget.RecyclerView>

然后再创建Recyclerview中的子布局msg_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/msg_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:layout_margin="4dp"/>
</LinearLayout>

创建对应的适配器MsgAdapter.java

package com.example.pic;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> implements View.OnClickListener{
    private List<Msg > msgList;
    RecyclerView recyclerView;
    static class ViewHolder extends RecyclerView.ViewHolder{
        LinearLayout layout;
        TextView textView;
        View msgView;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            layout=(LinearLayout)itemView.findViewById(R.id.msg_RecyclerView);
            textView=(TextView)itemView.findViewById(R.id.msg_title);
            msgView = itemView;
        }
    }

    public void setMsgList(List<Msg> msgList) {
        this.msgList = msgList;
    }

    public MsgAdapter(List<Msg> msgList){
        this.msgList=msgList;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
        view.setOnClickListener(this);
        final ViewHolder holder=new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull MsgAdapter.ViewHolder holder, int position) {
        Msg msg=msgList.get(position);
        holder.textView.setText(msg.getTitle()+": "+msg.getMsg());
    }

    @Override
    public int getItemCount() {
        return msgList.size();
    }
}

再到MainActivity.java中获取文件信息显示图片的代码后加入如下代码

recyclerView=(RecyclerView)findViewById(R.id.msg_RecyclerView);
                        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
                        recyclerView.setLayoutManager(layoutManager);
                        adapter=  new MsgAdapter(list);
                        recyclerView.setAdapter(adapter);

获取到的信息就可以显示到屏幕上了,并且如果信息比较多屏幕显示不全的话还可以滑动查看。

第三步,点击信息对Exif进行修改

GetExfi.java中添加修改Exif信息的方法

public void setexif(int id,String path,String data){
        try{
            ExifInterface exifInterface = new ExifInterface(path);
            Log.d("setexfi", "id:"+id+"path:"+path+"data:"+data);
            switch(id){
                case 0:
                    exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,data);
                    break;
                case 1:
                    exifInterface.setAttribute(ExifInterface.TAG_DATETIME,data);
                    break;
                case 2:
                    exifInterface.setAttribute(ExifInterface.TAG_MAKE,data);
                    break;
                case 3:
                    exifInterface.setAttribute(ExifInterface.TAG_MODEL,data);
                    break;
                case 4:
                    exifInterface.setAttribute(ExifInterface.TAG_FLASH,data);
                    break;
                case 5:
                    exifInterface.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,data);
                    break;
                case 6:
                    exifInterface.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,data);
                    break;
                case 7:
                    exifInterface.setAttribute(ExifInterface.TAG_EXPOSURE_TIME,data);
                    break;
                case 8:
                    exifInterface.setAttribute(ExifInterface.TAG_APERTURE,data);
                    break;
                case 9:
                    exifInterface.setAttribute(ExifInterface.TAG_ISO,data);
                    break;
                case 10:
                    exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE,data);
                    break;
                case 11:
                    exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE,data);
                    break;
            }
            exifInterface.saveAttributes();
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

MsgAdapter.java中添加点击事件
详见:https://jhcloud.top/blog/?p=2260

/*
    定义单击事件回调接口
     */
    public interface OnItemClickListener{
        //参数(父组件,当前单击的View,单击的View的位置,数据)
        void onItemClick(RecyclerView parent,View view, int position, Msg data);
    }
    /*
    在RecyclerView的Adapyer中声明该接口,并提供setter方法
     */
    private OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(OnItemClickListener onItemClickListener){
        this.onItemClickListener = onItemClickListener;
    }
    /*
    实现View.OnClickListener接口,并重写onClick(View view)方法,然后设置给接口的事件监听
     */
    @Override
    public void onClick(View v) {
        //根据RecyclerView获得当前View的位置
        int position = recyclerView.getChildAdapterPosition(v);
        //程序执行到此,会去执行具体实现的onItemClick()方法
        if (onItemClickListener!=null){
            onItemClickListener.onItemClick(recyclerView,v,position,msgList.get(position));
        }
    }
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        this.recyclerView= recyclerView;
    }

MainActivity.java中展示信息的代码下添加点击事件

                        adapter.setOnItemClickListener(new MsgAdapter.OnItemClickListener() {
                            @Override
                            public void onItemClick(RecyclerView parent, View view, int position, Msg data) {
                                Toast.makeText(MainActivity.this,data.getTitle()+":"+data.getMsg(),Toast.LENGTH_LONG).show();
                                showCustomizeDialog(data.getType(),data.getMsg());
                            }
                        });

MainActivity.java中添加Dialog

    //Dialog
    private void showCustomizeDialog(final int type,final String mag_data) {
        /* @setView 装入自定义View ==> R.layout.dialog_customize
         * 由于dialog_customize.xml只放置了一个EditView,因此和图8一样
         * dialog_customize.xml可自定义更复杂的View
         */
        AlertDialog.Builder customizeDialog =
                new AlertDialog.Builder(MainActivity.this);
        final View dialogView = LayoutInflater.from(MainActivity.this)
                .inflate(R.layout.modify,null);
        customizeDialog.setTitle("请输入修改后的值");
        customizeDialog.setView(dialogView);
        TextView textView = (TextView)dialogView.findViewById(R.id.modify_edit);
        textView.setText(mag_data);
        Toast.makeText(MainActivity.this,"请严格按照原有格式规范填写,否则可能导致软件崩溃或图片损坏!",Toast.LENGTH_LONG).show();
        customizeDialog.setPositiveButton("确定",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 获取EditView中的输入内容
                        EditText edit_text =
                                (EditText) dialogView.findViewById(R.id.modify_edit);
                        //String newdata=edit_text.
                        getExfi.setexif(type,path,edit_text.getText().toString());
                        list=getExfi.getExfi(path);
                        adapter.setMsgList(list);
                        recyclerView.setAdapter(adapter);
                        Log.d("setexfi", "final");
                    }
                });
        customizeDialog.show();
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注