Android 仿6.5以上版本微信相册

最近开发项目遇到一个功能,类似微信发朋友圈功能,查找资料并没有类似的效果,于是自己参照微信相册动手撸了一个,三天的开发时间比较仓促,有可能会有BUG,希望各撸友们批评指正(QQ:330093887或者QQ邮箱)。在开发此功能的过程中踩了不少坑,也得到不少的经验,特此在这里写博客记录一下。

先上效果图:


1.特点
· 使用“调整图像”将您的相片调整至尽善尽美
· 使用“选择性调整”对相片的特定对象或区域加以美化
·提供各种有趣且极具新意的滤镜,例如“怀旧”、“戏剧”、“复古”、“杂质”以及“移轴”
· 提供许多优质相框,可为美化相片起到画龙点睛的效果
2.功能
(1) 基本调整功能
·选择性调整-图片编辑库使用c++代码,在数秒钟内对相片中的特定区域做出精准的选择和增强。
·调整图像-使用“环境”来制造特别适合色彩和纹理的深度和自然饱和度。调整“白平衡”、“饱和度”和“对比度”等等。
·拉直旋转-使用简单的手势控制旋转90°及/或拉直相片。
·裁切-使用标准的纵横比或自由裁切,轻松裁切图像以去除相片上分散注意力的部分。
(2) 创造性增强
·黑白-此滤镜的灵感源于暗室,可为相片创造经典的黑白外观。
·复古胶片-使任何相片看起来像50年代、60年代或70年代的古老彩色胶片照。
·戏剧-透过为您的相片量身定制的效果来增添风格,从细微的纹理到异想天开的艺术效果都信手拈来。
·杂质-使您的相片呈现完全独特的时尚昏暗外观。
·移轴镜摄影-建立窄聚焦带,用以模拟微缩场景中常见的景深外观。
·中心焦点-透过模糊和调节周围背景的亮度来突出相片的拍摄主体。
·有机相框-为相片增加风格化边框,以达到画龙点睛的完美效果。
3.图片编辑库
{"滤镜","图像变形","剪切","涂鸦","边框","添加文字","添加水印","马赛克","增强","旋转"}等功能底层使用c++完成,达到快速处理图片不卡顿。
4.注意事项:
1,权限问题2,对图片操作过程中处理Bitmap需要谨慎3,数据保存以及异步操作4,更新UI……
5.代码片段:
(1)启动相册

public void selectPhoto() {
        Intent photoIntent = new Intent(this, ImageGridActivity.class);
        photoIntent.putExtra(ImagePicker.MAX_PHOTO_NUMBER, 4);
        startActivityForResult(photoIntent, CommonUtils.REQUEST_CODE_ALBUN);
    }

(2)得到图片路径

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == CommonUtils.REQUEST_CODE_ALBUN) {
            if (data != null) {
                try {
                    final ArrayList<ImageItem> images = (ArrayList<ImageItem>) data.getSerializableExtra(ImagePicker.EXTRA_RESULT_ITEMS);
                    int size = images.size();
                    for (int i = 0; i < size; i++) {
                        mThumbIds.add(images.get(i).path);
                    }
                    imageAdapter.notifyDataSetChanged();
                } catch (Exception e) {
                    Toast.makeText(this, "请您打开读取存储文件权限", Toast.LENGTH_LONG).show();
                    e.printStackTrace();
                }
            }
        }
    }

(3)调用各个功能CompileBitmapActivity

package injection.sw.com.mycocularlater.imagepicker.ui;

import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;

import java.io.File;

import cn.jarlen.photoedit.activity.AddTextActivity;
import cn.jarlen.photoedit.activity.AddWatermarkActivity;
import cn.jarlen.photoedit.activity.DrawBaseActivity;
import cn.jarlen.photoedit.activity.EnhanceActivity;
import cn.jarlen.photoedit.activity.ImageFilterActivity;
import cn.jarlen.photoedit.activity.ImagePasteActivity;
import cn.jarlen.photoedit.activity.MosaicActivity;
import cn.jarlen.photoedit.activity.PhotoFrameActivity;
import cn.jarlen.photoedit.activity.RevolveActivity;
import cn.jarlen.photoedit.activity.WarpActivity;
import injection.sw.com.mycocularlater.CommonUtils;
import injection.sw.com.mycocularlater.R;
import injection.sw.com.mycocularlater.imagepicker.Utils;
import injection.sw.com.mycocularlater.imagepicker.adapter.CompileBitmapAdapter;

import static injection.sw.com.mycocularlater.imagepicker.ui.ImagePreviewActivity.DATA_OF_IMAGE_PATH;


/**
 * Created by zhouqiong on 2017/6/6.
 */

public class CompileBitmapActivity extends TranslucentActivity implements View.OnClickListener {

    private RecyclerView recyclerView;
    private CompileBitmapAdapter adapter;
    private Class<?> intentClass;
    private int intentType = 0;
    private String cameraPath = null;
    private ImageView pictureShow, backImageView;
    private TextView saveTextView;
    private String[] str;
    private FrameLayout bannerFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_compile_bitmap);
        initView();
        initData();
        setOnClickListener();
    }

    private void initView() {
        setArgument();
        recyclerView = (RecyclerView) findViewById(R.id.bottom_gallery);
        pictureShow = (ImageView) findViewById(R.id.pictureShow);
        backImageView = (ImageView) findViewById(R.id.back_btn);
        saveTextView = (TextView) findViewById(R.id.save_btn);
        bannerFragment = (FrameLayout) findViewById(R.id.banner);
        Glide.with(this).load(cameraPath).into(pictureShow);
    }

    private void initData() {
        //设置布局管理器
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        str = new String[]{"滤镜", "图像变形", "剪切", "涂鸦", "边框", "添加文字", "添加水印", "马赛克", "增强", "旋转"};
        recyclerView.setLayoutManager(linearLayoutManager);
        //设置适配器
        adapter = new CompileBitmapAdapter(this, str);
        recyclerView.setAdapter(adapter);
    }

    private void setOnClickListener() {
        backImageView.setOnClickListener(this);
        saveTextView.setOnClickListener(this);
        pictureShow.setOnClickListener(this);
        adapter.setOnItemClickListener(new CompileBitmapAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                switch (position) {
                    case 0:
                        intentClass = ImageFilterActivity.class;
                        intentType = CommonUtils.PHOTO_FILTER_WITH_DATA;
                        break;
                    case 1:
                        intentClass = WarpActivity.class;
                        intentType = CommonUtils.PHOTO_WARP_WITH_DATA;
                        break;
                    case 2:
                        intentClass = ImagePasteActivity.class;
                        intentType = CommonUtils.PHOTO_CROP_WITH_DATA;
                        break;
                    case 3:
                        intentClass = DrawBaseActivity.class;
                        intentType = CommonUtils.PHOTO_DRAW_WITH_DATA;
                        break;
                    case 4:
                        intentClass = PhotoFrameActivity.class;
                        intentType = CommonUtils.PHOTO_FRAME_WITH_DATA;
                        break;
                    case 5:
                        intentClass = AddTextActivity.class;
                        intentType = CommonUtils.PHOTO_ADD_TEXT_DATA;
                        break;
                    case 6:
                        intentClass = AddWatermarkActivity.class;
                        intentType = CommonUtils.PHOTO_ADD_WATERMARK_DATA;
                        break;
                    case 7:
                        intentClass = MosaicActivity.class;
                        intentType = CommonUtils.PHOTO_MOSAIC_WITH_DATA;
                        break;
                    case 8:
                        intentClass = EnhanceActivity.class;
                        intentType = CommonUtils.PHOTO_ENHANCE_WITH_DATA;
                        break;
                    case 9:
                        intentClass = RevolveActivity.class;
                        intentType = CommonUtils.PHOTO_REVOLVE_WITH_DATA;
                        break;
                    default:
                        intentClass = null;
                        intentType = 0;
                        break;
                }

                if (cameraPath == null) {
                    Toast.makeText(CompileBitmapActivity.this, "请选择图片",
                            Toast.LENGTH_SHORT).show();
                    return;
                }
                if (intentClass == null) {
                    Toast.makeText(CompileBitmapActivity.this, "请图片操作类型",
                            Toast.LENGTH_SHORT).show();
                    return;
                }

                // 将图片路径photoPath传到所要调试的模块
                Intent photoFrameIntent = new Intent(CompileBitmapActivity.this, intentClass);
                photoFrameIntent.putExtra("camera_path", cameraPath);
                CompileBitmapActivity.this.startActivityForResult(photoFrameIntent, intentType);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) {
            return;
        }
        switch (requestCode) {
            case CommonUtils.PHOTO_FRAME_WITH_DATA:
            case CommonUtils.PHOTO_MOSAIC_WITH_DATA:
            case CommonUtils.PHOTO_DRAW_WITH_DATA:
            case CommonUtils.PHOTO_CROP_WITH_DATA:
            case CommonUtils.PHOTO_FILTER_WITH_DATA:
            case CommonUtils.PHOTO_ENHANCE_WITH_DATA:
            case CommonUtils.PHOTO_REVOLVE_WITH_DATA:
            case CommonUtils.PHOTO_WARP_WITH_DATA:
            case CommonUtils.PHOTO_ADD_WATERMARK_DATA:
            case CommonUtils.PHOTO_ADD_TEXT_DATA:
                cameraPath = data.getStringExtra("camera_path");
                Glide.with(this)
                        .load(Uri.fromFile(new File(cameraPath)))
                        .centerCrop()
                        .placeholder(R.mipmap.default_image)
                        .into(pictureShow);
                break;
        }
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.back_btn) {
            finish();
        } else if (id == R.id.save_btn) {
            File file = Utils.saveBitmap(this, BitmapFactory.decodeFile(cameraPath), "" + SystemClock.currentThreadTimeMillis() + ".jpg");
            Intent mIntent = new Intent();
            mIntent.putExtra(DATA_OF_IMAGE_PATH, file.getAbsolutePath());
            this.setResult(RESULT_OK, mIntent);
            finish();
        } else if (id == R.id.pictureShow) {
            //点击图片,其他view隐藏
            onImageSingleTap();
        }
    }

    public void onImageSingleTap() {
        if (bannerFragment.getVisibility() == View.VISIBLE) {
            showToolBar(AnimationUtils.loadAnimation(this, R.anim.top_out), AnimationUtils.loadAnimation(this, R.anim.fade_out), View.GONE);
        } else {
            showToolBar(AnimationUtils.loadAnimation(this, R.anim.top_in), AnimationUtils.loadAnimation(this, R.anim.fade_in), View.VISIBLE);
        }
    }

    private void showToolBar(Animation animation, Animation animation2, int visible) {
        bannerFragment.setAnimation(animation);
        bannerFragment.setVisibility(visible);
        bannerFragment.setAnimation(animation);
        recyclerView.setAnimation(animation2);
        bannerFragment.setVisibility(visible);
        recyclerView.setVisibility(visible);
    }

    private void setArgument() {
        Bundle bundle = getIntent().getExtras();
        cameraPath = bundle.getString("bitmap");
    }
}

(4)判断某个点是否在多边形区域内

package cn.jarlen.photoedit.operate;

import android.graphics.PointF;
import android.util.Log;

import java.util.List;


/**
 * 判断某个点是否在多边形区域内
 * Created by zhouqiong on 2017/6/5.
 */
public class Lasso {
    private float[] mPolyX, mPolyY;
    private int mPolySize;

    /**
     * 构造方法
     *
     * @param
     */
    public Lasso(List<PointF> pointFs) {
        this.mPolySize = pointFs.size();

        this.mPolyX = new float[this.mPolySize];
        this.mPolyY = new float[this.mPolySize];

        for (int i = 0; i < this.mPolySize; i++) {
            this.mPolyX[i] = pointFs.get(i).x;
            this.mPolyY[i] = pointFs.get(i).y;
        }

        Log.d("lasso", "lasso size:" + mPolySize);
    }

    /**
     * 判断多边形是否包含点
     *
     * @param x X坐标
     * @param y Y坐标
     * @return true
     */
    public boolean contains(float x, float y) {
        boolean result = false;

        for (int i = 0, j = mPolySize - 1; i < mPolySize; j = i++) {
            if ((mPolyY[i] < y && mPolyY[j] >= y)
                    || (mPolyY[j] < y && mPolyY[i] >= y)) {
                if (mPolyX[i] + (y - mPolyY[i]) / (mPolyY[j] - mPolyY[i])
                        * (mPolyX[j] - mPolyX[i]) < x) {
                    result = !result;
                }
            }
        }
        return result;
    }
}

(5)马赛克效果代码片段

 public static Bitmap getMosaic(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int radius = 10;


        Bitmap mosaicBitmap = Bitmap.createBitmap(width, height,
                Config.ARGB_8888);
        Canvas canvas = new Canvas(mosaicBitmap);

        int horCount = (int) Math.ceil(width / (float) radius);
        int verCount = (int) Math.ceil(height / (float) radius);

        Paint paint = new Paint();
        paint.setAntiAlias(true);

        for (int horIndex = 0; horIndex < horCount; ++horIndex) {
            for (int verIndex = 0; verIndex < verCount; ++verIndex) {
                int l = radius * horIndex;
                int t = radius * verIndex;
                int r = l + radius;
                if (r > width) {
                    r = width;
                }
                int b = t + radius;
                if (b > height) {
                    b = height;
                }
                int color = bitmap.getPixel(l, t);
                Rect rect = new Rect(l, t, r, b);
                paint.setColor(color);
                canvas.drawRect(rect, paint);
            }
        }
        canvas.save();

        return mosaicBitmap;
    }

Paste_Image.png

特别感谢
特别感谢大神@jarlen,使用了@jarlen底层C++对图片处理,大大减少了代码量,类似与美图秀秀。但是具体得不到大神的项目地址和联系方式@jarlen的名字也只是在朋友给我的demo中看到的,这里表达我对大神的崇高敬意。
源码
源码下载地址github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 总结: 1、和枚举一模一样 2、使用typedef的好处之一:在使用枚举类型的时候可以省略一个enum的关键字。这...
    人话博客阅读 613评论 0 50
  • 《卡桑德拉大桥》是一部很老的生化灾难片,拍自1976年。没有任何以假乱真的电影特效,没有华丽的灯光和繁琐的场景,整...
    Kazehana风花阅读 3,550评论 1 1
  • 在图书馆自习到10.20.位置让人舒适安心,平淡的看书渐入佳境的感觉很喜欢。 第一周小组准备财管实习,作为市场总监...
    Dorisyoung阅读 144评论 0 0