Android用kotlin修改一个图片编辑库做一个作业点评的功能

本文介绍了如何使用Kotlin对一个图片编辑库进行改造,以适应作业点评的需求。首先,通过优化首页图片加载,使用Glide和Bitmap压缩避免页面崩溃。接着,实现了从图片列表中选择图片,下载并压缩后再进行编辑,确保本地相册不会积累过多图片。同时,文章提供了图片下载、保存到相册以及二进制转换为Bitmap的相关工具类。

准备工作

  • github 找一个图片编辑器 地址,这个图就是对单个本地图片进行编辑,加载大图有问题,页面传递bitmap有问题
  • 目前已有功能:编辑一张本地图片
  • 想要效果:传入图片下载list,第一个页面要有图片切换效果,点击按钮对图片进行编辑,图片保存

优化

  • 首页直接glide加载图片,viewpager左右切换,对bitmap压缩防止过大导致页面崩溃
  • 点击进入编辑页的时候,对对应的图片进行下载,压缩,之后再编辑,跳转页面的时候传下载压缩后的路径,而不是传递bitmap
  • 因为编辑的时候是对本地图编辑,会有图片下载,保证本地相册不会过多下载图片

代码

首页传入图片集合,进行展示,对图片编辑需要申请权限

package me.minetsh.imaging

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri

import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentPagerAdapter
import android.support.v4.view.PagerAdapter
import android.support.v4.view.ViewPager
import android.support.v7.app.AppCompatActivity

import android.view.View
import android.view.ViewGroup
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.ToastUtils
import com.htjy.app.common_util.ext.clickWithTrigger
import com.htjy.app.common_util.util.LuBanUtils

import com.tbruyelle.rxpermissions2.RxPermissions
import kotlinx.android.synthetic.main.image_activity_img_handle.*
import kotlinx.android.synthetic.main.image_edit_ready.*


/**
Func:点评的首页
# 老师点评图片不可超过9张,超过时不可再点击”红笔点评“按钮,点击提示:“点评图片不可超过9张”
# 点击老师点评的图片可继续点评,保存后替换原点评图片
# 点评跳转的是新页面-仿微信
# 点评不能左右翻动
# 点评的时候不传递bitmap,而是传递路径
 */
const val EDIT_IMG_REQUESTCODE = 1000

class IMGHandleActivity : AppCompatActivity() {

    private lateinit var dataList: ArrayList<String>  //数据源
    private var type: Int = 0   //0 是网络  1是本地

    //测试的网络数据
    private val testUrl = arrayListOf("https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MAAL-USZ-F0(0)011.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MAAM-USZ-F0(0)012.png"
            , "https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MAAA-USZ-F0(0)001.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MAAB-USZ-F0(0)002.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MAEC-USZ-F0(0)099.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL-C-MACD-USZ-F0(0)052.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL_C_MAGY_USZlow_167_R_ad.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL_C_MAHB_USZlow_170_R_ad.png",
            "https://animal-crossing.com/amiibo/assets/img/cards/NVL_C_MALZ_USZlow_264_R_ad.png")
    //测试的本地数据
    private var test = arrayListOf(
            "/storage/emulated/0/DCIM/Screenshots/Screenshot_2019-10-30-10-09-35-727_com.htjy.gardener.component_remark_teacher.png", "/storage/emulated/0/BesttimerCamera2/picture_1574761262840.jpg")


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.image_activity_img_handle)
        //EventBus.getDefault().register(IMGHandleActivity@ this)
        initData()
        //initView()
    }


    override fun onDestroy() {
        super.onDestroy()
        //EventBus.getDefault().unregister(IMGHandleActivity@ this)
    }


    companion object {
        fun launch(from: Activity, data: ArrayList<String>, type: Int) {
            val intent = Intent(from, IMGHandleActivity::class.java)
            intent.putExtra("type", type)
            intent.putStringArrayListExtra("data", data)
            from.startActivityForResult(intent, EDIT_IMG_REQUESTCODE)
        }
    }


    private fun initData() {
        //dataList = testUrl
        dataList = intent.getStringArrayListExtra("data")
        type = intent.getIntExtra("type", 0)
        //申请读写权限
        //Singleton().test(arrayListOf("1","2","3"))
        RxPermissions(IMGHandleActivity@ this)
                .request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                .subscribe {
                    if (it) {
                        initView()
                    } else {
                        finish()
                    }
                }


    }


   

    private var currentPage = 0
    private val fragmentList = arrayListOf<ItemFragment>()
    private fun initView() {
        //获取Bitmap
        //val layoutInflater = LayoutInflater.from(IMGHandleActivity@ this)

        for (value in dataList) {
            fragmentList.add(ItemFragment.launch(value))
        }

        val adapter = MyItemFragmentPagerAdapter(supportFragmentManager, fragmentList)
        vp_images.adapter = adapter
        vp_images.offscreenPageLimit = dataList.size
        tv_count.text = "${currentPage + 1}/${dataList.size}"
        vp_images.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {

            }

            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {

            }

            override fun onPageSelected(position: Int) {
                currentPage = position
                tv_count.text = "${currentPage + 1}/${dataList.size}"
            }
        })

        /**
         * 点评:如果是网络图片必须要先下载才能进入点评,步骤为:下载,copy,压缩
         */
        btn_dianping.clickWithTrigger {

            when (type) {
                0 -> {
                    LuBanUtils.loadPicFromNet(dataList[currentPage], IMGHandleActivity@ this) { isSuccess, path ->
                        if (isSuccess) {
                            IMGEditActivity.launch(path, this@IMGHandleActivity)
                        }
                    }
                }
                1 -> {
                    LuBanUtils.loadPicFromLocal(dataList[currentPage], IMGHandleActivity@ this) { isSuccess, path ->
                        if (isSuccess) {
                            IMGEditActivity.launch(path, this@IMGHandleActivity)
                        }
                    }
                }
            }

        }

        btn_save.clickWithTrigger {
            //保存
            when (type) {
                0 -> {
                    LuBanUtils.downloadPicFromNet(dataList[currentPage], IMGHandleActivity@ this) { isSuccess, path ->
                        if (isSuccess) {
                            // 最后通知图库更新
                            sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://$path")))
                            ToastUtils.showShort("图片保存成功")
                        } else {
                            ToastUtils.showShort("图片保存失败")
                        }
                    }
                }
//                1 -> {
//                    LuBanUtils.loadPicFromLocal(dataList[currentPage], IMGHandleActivity@ this) { isSuccess, path ->
//                        if (isSuccess) {
//                            IMGEditActivity.launch(path, this@IMGHandleActivity)
//                        }
//                    }
//                }
            }

        }
    }


    private inner class MyItemFragmentPagerAdapter(fm: FragmentManager, var data: List<Fragment>) : FragmentPagerAdapter(fm) {
        override fun getItem(position: Int): Fragment {
            return data[position]
        }

        override fun getCount(): Int {
            return data.size
        }

    }


    private inner class MyImgViewPagerAdapter(var context: Context, var data: List<View>) : PagerAdapter() {
        override fun isViewFromObject(view: View, obj: Any): Boolean {
            return view == obj
        }

        override fun getCount(): Int {
            return data.size
        }

        override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
            container.removeView(data[position])
        }

        override fun instantiateItem(container: ViewGroup, position: Int): Any {
            container.addView(data[position])
            return data[position]
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == EDIT_IMG_REQUESTCODE && resultCode == Activity.RESULT_OK) {
            //编辑图片成功的返回
            data?.let {
                val savePath = it.getStringExtra(IMGEditActivity.EXTRA_IMAGE_SAVE_PATH)
                LogUtils.d("保存图片的绝对路径==上级页面收到>>>$savePath")
                setResult(EDIT_IMG_REQUESTCODE, it)
                finish()
            }
        }
    }
}

图片编辑页的修改

package me.minetsh.imaging;

import android.app.Activity;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import android.text.TextUtils;

import com.blankj.utilcode.util.LogUtils;


import me.minetsh.imaging.core.IMGMode;
import me.minetsh.imaging.core.IMGText;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

import static me.minetsh.imaging.IMGHandleActivityKt.EDIT_IMG_REQUESTCODE;

public class IMGEditActivity extends IMGEditBaseActivity {

    private static final int MAX_WIDTH = 1024;

    private static final int MAX_HEIGHT = 1024;

    public static final String EXTRA_IMAGE_URI = "IMAGE_URI";

    public static final String EXTRA_IMAGE_SAVE_PATH = "IMAGE_SAVE_PATH";


    public static void launch(String url, Activity fromContext) {
        Intent intent = new Intent(fromContext, IMGEditActivity.class);
        intent.putExtra("bitmap", url);
        //默认一个编辑图片后的路径
        intent.putExtra(EXTRA_IMAGE_SAVE_PATH, UUID.randomUUID().toString() + ".jpg");
        fromContext.startActivityForResult(intent, EDIT_IMG_REQUESTCODE);
    }

    @Override
    public void onCreated() {

    }

    private File currentFile;

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (currentFile != null && currentFile.exists()) {
            currentFile.delete();
        }
    }

    @Override
    public Bitmap getBitmap() {
        String url = getIntent().getStringExtra("bitmap");
        // String localPath = LuBanUtils.INSTANCE.getPicMap().get(url);
        currentFile = new File(url);
        Bitmap bitmap = BitmapFactory.decodeFile(currentFile.getPath());


        // Bitmap bitmap = getIntent().getParcelableExtra("bitmap");

//        Intent intent = getIntent();
//        if (intent == null) {
//            return null;
//        }
//
//        Uri uri = intent.getParcelableExtra(EXTRA_IMAGE_URI);
//        if (uri == null) {
//            return null;
//        }
//
//        IMGDecoder decoder = null;
//
//        String path = uri.getPath();
//        if (!TextUtils.isEmpty(path)) {
//            switch (uri.getScheme()) {
//                case "asset":
//                    decoder = new IMGAssetFileDecoder(this, uri);
//                    break;
//                case "file":
//                    decoder = new IMGFileDecoder(uri);
//                    break;
//            }
//        }
//
//        if (decoder == null) {
//            return null;
//        }
//
//        BitmapFactory.Options options = new BitmapFactory.Options();
//        options.inSampleSize = 1;
//        options.inJustDecodeBounds = true;
//
//        decoder.decode(options);
//
//        if (options.outWidth > MAX_WIDTH) {
//            options.inSampleSize = IMGUtils.inSampleSize(Math.round(1f * options.outWidth / MAX_WIDTH));
//        }
//
//        if (options.outHeight > MAX_HEIGHT) {
//            options.inSampleSize = Math.max(options.inSampleSize,
//                    IMGUtils.inSampleSize(Math.round(1f * options.outHeight / MAX_HEIGHT)));
//        }
//
//        options.inJustDecodeBounds = false;
//
//        Bitmap bitmap = decoder.decode(options);
//        if (bitmap == null) {
//            return null;
//        }

        return bitmap;
    }

    @Override
    public void onText(IMGText text) {
        mImgView.addStickerText(text);
    }

    @Override
    public void onModeClick(IMGMode mode) {
        IMGMode cm = mImgView.getMode();
        if (cm == mode) {
            mode = IMGMode.NONE;
        }
        mImgView.setMode(mode);
        updateModeUI();

        if (mode == IMGMode.CLIP) {
            setOpDisplay(OP_CLIP);
        }
    }

    @Override
    public void onUndoClick() {
        IMGMode mode = mImgView.getMode();
        if (mode == IMGMode.DOODLE) {
            mImgView.undoDoodle();
        } else if (mode == IMGMode.MOSAIC) {
            mImgView.undoMosaic();
        }
    }

    @Override
    public void onCancelClick() {
        onBackPressed();
    }

    @Override
    public void onDoneClick() {

        File file = new File(getCacheDir(), getIntent().getStringExtra(EXTRA_IMAGE_SAVE_PATH));
        String path = file.getAbsolutePath();
        if (!TextUtils.isEmpty(path)) {
            Bitmap bitmap = mImgView.saveBitmap();
            if (bitmap != null) {
                FileOutputStream fout = null;
                try {
                    fout = new FileOutputStream(path);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fout);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } finally {
                    if (fout != null) {
                        try {
                            fout.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                Intent intent = new Intent();
                //把保存的图片的绝对路径返回出去
                intent.putExtra(EXTRA_IMAGE_SAVE_PATH, path);
                LogUtils.d("保存图片的绝对路径>>>" + path);
                setResult(RESULT_OK, intent);
                finish();
                return;
            }
        }
        setResult(RESULT_CANCELED);
        finish();
    }

    @Override
    public void onCancelClipClick() {
        mImgView.cancelClip();
        setOpDisplay(mImgView.getMode() == IMGMode.CLIP ? OP_CLIP : OP_NORMAL);
    }

    @Override
    public void onDoneClipClick() {
        mImgView.doClip();
        setOpDisplay(mImgView.getMode() == IMGMode.CLIP ? OP_CLIP : OP_NORMAL);
    }

    @Override
    public void onResetClipClick() {
        mImgView.resetClip();
    }

    @Override
    public void onRotateClipClick() {
        mImgView.doRotate();
    }

    @Override
    public void onColorChanged(int checkedColor) {
        mImgView.setPenColor(checkedColor);
    }
}

图片下载压缩,过多文件删除的工具类

package com.htjy.app.common_util.util


import android.content.Context
import android.os.Environment
import com.blankj.utilcode.util.LogUtils
import com.htjy.baselibrary.widget.imageloader.ImageLoaderUtil
import com.htjy.baselibrary.widget.imageloader.listener.ImageDownloadListener
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import top.zibin.luban.Luban
import top.zibin.luban.OnCompressListener
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException


/**
 * 鲁班压缩工具类
 */
object LuBanUtils {
 

  

    /**
     * 复制文件
     *
     * @param source 输入文件
     * @param target 输出文件
     */
    private fun copy(source: File?, callBack: (pathFile: File) -> Unit) {
        //获取到下载得到的图片,进行本地保存
        //获取到下载得到的图片,进行本地保存
        val pictureFolder: File = Environment.getExternalStorageDirectory()
        //第二个参数为你想要保存的目录名称
        //第二个参数为你想要保存的目录名称
        val appDir = File(pictureFolder, "my_edit_pic")
        if (!appDir.exists()) {
            appDir.mkdirs()
        }
        val fileName = System.currentTimeMillis().toString() + ".jpg"
        val destFile = File(appDir, fileName)
        var fileInputStream: FileInputStream? = null
        var fileOutputStream: FileOutputStream? = null
        try {
            fileInputStream = FileInputStream(source)
            fileOutputStream = FileOutputStream(destFile)
            val buffer = ByteArray(1024)
            while (fileInputStream.read(buffer) > 0) {
                fileOutputStream.write(buffer)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                fileInputStream?.close()
                fileOutputStream?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            //删掉源文件
            if (source != null && source.exists()) {
                source.delete()
            }
            callBack.invoke(destFile)
        }
    }


 
   private  fun deleteFile(file: File?) {
        if (file != null && file.exists()) {
            file.delete()
        }
    }

    /**
     * 下载图片 copy  压缩
     */
    fun loadPicFromNet(uri: String, context: Context, callBack: (isSuccess: Boolean, path: String?) -> Unit) {
        Flowable.just(uri).subscribeOn(Schedulers.io()).map {
            //下载图片
            ImageLoaderUtil.getInstance().downloadOnly(context, uri, object : ImageDownloadListener {
                override fun onSuccess(path: String?) {
                    LogUtils.d("鲁班>>>Gilde下载图片成功==${uri}")
                    LogUtils.d("鲁班>>>Gildecopy图片")
                    path?.let {
                        val file = File(it)
                        if (file != null && file.exists()) {
                            //这里只是缓存文件,还需要copy成正式文件
                            copy(file) { targetFile ->
                                LogUtils.d("鲁班==copy成功===${targetFile.absolutePath}")
                                LogUtils.d("鲁班>>>压缩图片")
                                Luban.with(context)
                                        .load(targetFile)
                                        .setTargetDir(targetFile.parent)
                                        .ignoreBy(10)
                                        .setCompressListener(object : OnCompressListener {
                                            override fun onSuccess(file: File?) {
                                                LogUtils.d("鲁班>>>压缩成功==${uri}===${file?.absolutePath}")
                                                //删掉copy的文件
                                                deleteFile(targetFile)
                                                callBack.invoke(true, file?.absolutePath)
                                            }

                                            override fun onError(e: Throwable?) {
                                                LogUtils.d("鲁班>>>压缩失败==${uri}===${e?.message}")
                                                callBack.invoke(false, null)
                                            }

                                            override fun onStart() {
                                                LogUtils.d("鲁班>>>开始压缩==${uri}===")
                                            }

                                        })
                                        .launch()
                            }
                        }
                    }

                }

                override fun onFail() {
                    LogUtils.d("鲁班>>>Gilde下载图片失败==${uri}")
                    callBack.invoke(false, null)
                }
            })
        }.subscribe()
    }

    /**
     * 加载本地图片并压缩
     */
    fun loadPicFromLocal(path: String, context: Context, callBack: (isSuccess: Boolean, path: String?) -> Unit) {
        //对本地图做一定的压缩就行了
        Flowable.just(path).subscribeOn(Schedulers.io()).map { uri ->
            val file = File(uri)
            LogUtils.d("鲁班>>>开始压缩==${uri}")
            if (file != null && file.exists()) {
                Luban.with(context)
                        .load(file)
                        .setCompressListener(object : OnCompressListener {
                            override fun onSuccess(file: File?) {
                                if (file != null && file.exists()) {
                                    LogUtils.d("鲁班>>>压缩成功==${uri}===${file?.absolutePath}")
                                    callBack.invoke(true, file?.absolutePath)
                                    //target.onNext("")
                                }
                            }

                            override fun onError(e: Throwable?) {
                                LogUtils.d("鲁班>>>压缩失败==${uri}===${e?.message}")
                                callBack.invoke(false, null)
                            }

                            override fun onStart() {
                                LogUtils.d("鲁班>>>开始压缩==${uri}===")
                            }

                        })
                        .launch()
            }
        }.subscribe()

    }


    /**
     * 下载线上的图片
     */
    fun downloadPicFromNet(uri: String, context: Context, callBack: (isSuccess: Boolean, path: String?) -> Unit) {
        Flowable.just(uri).subscribeOn(Schedulers.io()).map {
            //下载图片
            ImageLoaderUtil.getInstance().downloadOnly(context, uri, object : ImageDownloadListener {
                override fun onSuccess(path: String?) {
                    LogUtils.d("鲁班>>>Gilde下载图片成功==${uri}")
                    LogUtils.d("鲁班>>>Gildecopy图片")
                    path?.let {
                        val file = File(it)
                        if (file != null && file.exists()) {
                            //这里只是缓存文件,还需要copy成正式文件
                            copy(file) { targetFile ->
                                LogUtils.d("鲁班==下载成功===${targetFile.absolutePath}")
                                callBack.invoke(true, targetFile.absolutePath)
                            }
                        }
                    }

                }

                override fun onFail() {
                    LogUtils.d("鲁班>>>Gilde下载图片失败==${uri}")
                    callBack.invoke(false, null)
                }
            })
        }.subscribe()
    }
}

分享一个下载图片,并刷新手机相册的工具类

二进制bytearr转bitmap

    fun getBitmapFromByte(temp: ByteArray): Bitmap? {
        return if (temp != null) {
            BitmapFactory.decodeByteArray(temp, 0, temp.size)
        } else {
            null
        }
    }

下载图片保存,刷新相册

/**
     * 保存图片到图库
     *
     * @param context
     * @param bmp
     */
    public static void saveImageToGallery(Context context, Bitmap bmp, String picDirName) {
        // 首先保存图片
//        File appDir = new File(Environment.getExternalStorageDirectory(),
//                picDirName);
        File appDir = new File(getFilePath(context,picDirName));
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        LogUtils.d("下载啊目录>>"+appDir.getAbsolutePath());
        String fileName = System.currentTimeMillis() + ".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {

            ToastUtils.showShort("保存失败");
            e.printStackTrace();
        } catch (IOException e) {
            ToastUtils.showShort("保存失败");
            e.printStackTrace();
        }

        // 其次把文件插入到系统图库
        try {
            MediaStore.Images.Media.insertImage(context.getContentResolver(),
                    file.getAbsolutePath(), fileName, null);
            ToastUtils.showShort("保存成功");
        } catch (FileNotFoundException e) {
            ToastUtils.showShort("保存失败");
            e.printStackTrace();
        }
        // 最后通知图库更新
        context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                Uri.fromFile(file)));
    }


    public static String getFilePath(Context context, String dir) {
        String directoryPath = "";
        //判断SD卡是否可用
        if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            directoryPath = context.getExternalFilesDir(dir).getAbsolutePath();
            // directoryPath =context.getExternalCacheDir().getAbsolutePath() ;
        } else {
            //没内存卡就存机身内存
            directoryPath = context.getFilesDir() + File.separator + dir;
            // directoryPath=context.getCacheDir()+File.separator+dir;
        }
        File file = new File(directoryPath);
        if (!file.exists()) {//判断文件目录是否存在
            file.mkdirs();
        }
        LogUtil.i("filePath====>" + directoryPath);
        return directoryPath;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值