博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ByPhoto-秒开的安卓图片选择库
阅读量:2351 次
发布时间:2019-05-10

本文共 3716 字,大约阅读时间需要 12 分钟。

一、背景

ByPhoto是个安卓图片选择库, 在启动渲染速度上做了很多优化; 荣耀8真机测试,图库里有3000多张图片。 冷启动图片选择页渲染完成需800ms左右, 热启动(即第二次打开Activity)渲染需要300ms。 真正实现了秒开的用户体验。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二、需求
1、支持图片预加载, 即将图库的前几张图片加载到内存中; 使用了Glide的preload;
2、数据库分段回调, 即图片有几千张图片时, 每查询一定数量时(例如10条)就通知UI补充数据; RecyclerView不会刷新屏幕外的图片,只是缓存了文件路径; PS:这里还可以再优化一下,例如列表向下滑动时预加载后半段数据; 但考虑到字符串占用内存不大,几兆的样子,暂未实现;
3、数据结构, 使用适当的数据结构Map、Set降低读写时间复杂度;
4、支持手指在屏幕滑动时自动勾选经过的图片;
5、勾选图片时只刷新选中图标, 不刷新item图片;避免纵向滑动时刷新闪烁的问题;
6、支持设置单行图片数量和最多选中数量;
在这里插入图片描述

三、核心代码

类图
在子线程查询数据库,并分批通知UI数据变化;

@Override protected List
doInBackground(String... strings) { ... while (cursor.moveToNext()) { ... //每隔10个图片报一次, 即分段通知UI数据变化 if (i % Constants.PHOTO_COUNT_PER_TIME == 0 && i > 0) { ImageItem[] segData = new ImageItem[Constants.PHOTO_COUNT_PER_TIME]; for (int k = 0; k < Constants.PHOTO_COUNT_PER_TIME; k++) { segData[k] = itemList.get(i - Constants.PHOTO_COUNT_PER_TIME + k); } publishProgress(segData); } return null; }

预加载前几张图片到Glide缓存中, 默认加载前15张;

public static void preloadData(final Context ctx) {    ...          while (cursor.moveToNext() && i < Constants.MAX_PRELOAD_PHOTO_NUMS) {            final String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));            if (path != null && new File(path).exists()) {              Log.d("brycegao", "文件已存在:" + path);            }            sHandler.post(new Runnable() {              @Override public void run() {                Glide.with(ctx)                    .load(new File(path))                    .addListener(new RequestListener
() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target
target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target
target, DataSource dataSource, boolean isFirstResource) { return false; } }) .preload(Constants.getScreenWidth(context), Constants.getScreenWidth(context)); } }); i++; } ... }

在Activity的onCreate最开始启动线程加载数据, 注意:这里不会出现并发问题, 原因是子线程通过Handler执行UI线程的函数。 onCreate必须执行完成才可能响应子线程触发的回调;

protected void onCreate(Bundle savedInstanceState) {    //在子线程加载数据    initData();    super.onCreate(savedInstanceState);    ...}

重写RecyclerView并处理滑动事件, 逻辑是判断横向滑动时勾选经过的item, 纵向滑动时不做处理; 通过坐标(x,y)找到匹配的RecyclerView条目;

private boolean processTouchEvent(MotionEvent event) {    int x = (int) event.getX();    int y = (int) event.getY();    //记录点击屏幕时的初始坐标    if (event.getAction() == MotionEvent.ACTION_DOWN) {      mDownX = x;      mDownY = y;    }    //抬起手指时重置    if (event.getAction() == MotionEvent.ACTION_UP        || event.getAction() == MotionEvent.ACTION_CANCEL) {      mDownY = 0;      mDownX = 0;      mLastX = 0;      mLastY = 0;    }    if (event.getAction() == MotionEvent.ACTION_MOVE) {      double distance = Math.sqrt(Math.abs(x - mLastX) * Math.abs(x - mLastX)          + Math.abs(y - mLastY) * Math.abs(y - mLastY));      //如果是横向滑动且滑动距离超过阈值,则判断经过的item并勾选      if (distance > MIN_DISTANCE && Math.abs(x - mDownX) > Math.abs(y - mDownY)) {        mLastY = y;        mLastX = x;        doCheckSingleFinger(x, y);      }    }    return false;  }

控件:设置控件固定宽高, 减少测量时间;

四、总结

todo:手指滑动时取消勾选状态; 手指滑动时RecyclerView不动;

通过各种技术措施, 尽可能减少渲染时间; 目前最耗时的部分是Glide加载文件并绘制到控件;  监听DrawListener回调, 显示第一个图片(不是背景图,是目标图片)需要800ms左右(冷启动),UI体验看上去就是RecyclerView控件白了一下; 大概写了2天, 有一些收获。   欢迎技术交流~~~

转载地址:http://wkqvb.baihongyu.com/

你可能感兴趣的文章
学习笔记1之static
查看>>
学习笔记2之继承
查看>>
循环链表实现增、删、改、查等功能
查看>>
Android实现超链接和跑马灯
查看>>
实现二叉树先序、中序、后序遍历
查看>>
Socket客户端服务器连接
查看>>
简单字符设备驱动程序的操作步骤
查看>>
视频压缩:I帧、P帧、B帧
查看>>
视频编解码基础一
查看>>
视频编码学习二
查看>>
视频处理
查看>>
Python的安装教程
查看>>
谈谈码率、帧率、分辨率和清晰度
查看>>
OSI参考模型通信举例
查看>>
Vue.js 入门学习(一)
查看>>
Vue.js入门学习(二)实例、数据绑定、计算属性
查看>>
Vue.js入门学习(三) Class与Style绑定
查看>>
Vue.js入门学习(五)方法与事件处理器、表单控件绑定
查看>>
项目:Vue.js高仿饿了吗外卖APP(一)
查看>>
javascript中一些相对位置
查看>>