android学习之路(七)---- 用Fan-Image-Loader实现一个炫酷的相册功能

FanGallery
一、简介
        在上一篇博文当中,我们提出了universal-image-loader的缺点,并进行了丰富和改写,那么,这期我们就以上篇博文封装的Fan-Image-Loader为基础,实现一个相册, 一般来说,像这种相册功能,都有openGL来实现加载过程,以达到快速渲染的目的,但是openGL有很大的学习成本,而且扩展性不高,这里我们使用Fan-Image-Loader同样可以达到这样的目的

这里写图片描述


二、内存问题的解决
        对于android应用来讲,内存始终是一个永恒的话题,但对于一个相册app而言,图片的加载速度和内存都是让人头疼的问题,一般的解决办法,就是用openGL提高渲染速度,内存上面尚没有更好的解决办法,那么在本篇例子当中,我们使用Fan-Image-Loader加载图片,同样可以达到openGL的渲染速度。接下来就是内存!内存!内存!首先我们应该明白,在android生态圈,系统对应用运行时的堆内存分配没有严格标准,从60M~140M都有,如果app本身就消耗内存,那么留给相册和图像处理的内存就很少了,这时候就很容易OOM,但是另一点,android系统对应用的内存分配是以进程为单位的,这就给我们提供了迂回的解决办法:
        1.将工程分为两个进程:app和pic,app进程就是系统默认进程,启动时就会分配内存空间,pic进程的启动由一个service启动,相关的初始化放在service的onCreate方法当中。
        2.进程之间的通信有两种方式:aidl和broadcastReceiver,但是对于我们这样一个轻量的app而言,broadcastReceiver显然太重了,所以用aidl通信,当我们在app进程点击某个按钮,准备进入相册的时候,会通过aidl提供的方法判断图库当中是否有图片,如果没有图片,那么就应该启动拍照界面而不是相册界面。
        3.由于是两个进程,他们之间不会共用任何变量和资源,这就需要在各自的初始化入口处进行各自的初始化操作。
三、初始化
        从上面的解析当中,我们知道了用双进程来解决内存问题,如何启动一个双进程呢?

1.自定义一个service—–FanService.java

/**
 * time: 15/12/6
 * description:pic进程的启动入口
 *
 * @author fandong
 */
public class FanService extends Service {

    private class MixBinder extends IFanService.Stub {
        @Override
        public boolean isPhotoValidate() throws RemoteException {
            return LocalPhotoManager.getInstance().isPhotoValidate();
        }

        @Override
        public void putLocalPhoto(int code, String path) throws RemoteException {
            LocalPhotoManager.getInstance().put2Gallery(code, path);
            LocalPhotoManager.getInstance().put2Map(code, path);
        }
    }

    /*退出的时候,干掉mix进程*/
    public static void stopFanService(Context context) {
        //1.停掉LocalPhotoManager
        LocalPhotoManager.destroy();
        //2.停掉mix进程
        Intent intent = new Intent(context, FanService.class);
        context.stopService(intent);
    }


    /*启动mix进程*/
    public static void startFanService(Context context) {
        Intent intent = new Intent(context, FanService.class);
        context.startService(intent);
    }

    /*绑定mix进程*/
    public static void bindFanService(Context context, ServiceConnection connection) {
        Intent intent = new Intent(context, FanService.class);
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MixBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //1.扫描本地图片到内存当中
        LocalPhotoManager.getInstance().initialize();
        //2.初始化ImageLoader
        FanImageLoader.init(getApplicationContext(), FileUtil.getPathByType(FileUtil.DIR_TYPE_CACHE));
        //3.初始化Pinguo-image-loader当中的日志系统
        L.writeDebugLogs(BuildConfig.DEBUG);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        stopFanService(this);
        return super.onUnbind(intent);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }
}

2.在清单文件当中注册,并声明进程,核心就是android:proccess这个属性了:

<activity
    android:name=".GalleryActivity"
    android:process=":pic"
    android:theme="@style/AppTheme.NoActionBar" />

<service
    android:name=".FanService"
    android:process=":pic" />

        可以看到,这里我们把FanService和GalleryActivity放到了同一个进程:pic当中了,接下来就启动:pic进程了
3.启动pic进程,在GalleryApplication.java当中:

@Override
public void onCreate() {
    super.onCreate();
    gContext = this;
    //1.启动pic进程
    FanService.startFanService(this);
}

        从上面的FanService.java的onCreate方法当中可以看出来,我们在此方法当中进行了本地图片资源的初始化。

4.程序写到这一步,完成了同一应用的双进程运行,相当于整个app增加了一倍的堆内存,虽然不能根除OOM,但是也大幅降低了OOM的风险


四、实现酷炫的照片选择效果
    1.从上面的动画效果可以看出,当手指触碰到裁剪区域的时候,裁剪区域会随着相册整体向上滑动,整个交互流程如下图所示:
这里写图片描述
    2.上面的流程当中涉及到的关键技术如下所示:
        2.1 裁剪界面覆盖在RecyclerView上面,所有RecyclerView应该有一个headerView,此headerView的高度应该和裁剪区域一样,这样才能达到覆盖的效果,但是recyclerView并没有想ListView一样添加headerView的功能,所以只能通过如下两步操作,达到这样的效果:

第一步、给LayoutManager设置一行的item宽度:

mLayoutManager = new GridLayoutManager(this, 4, GridLayoutManager.VERTICAL, false);
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        if (position == 0) {
            return 4;//第一行宽度占四列
        }
        return 1;
    }
});
mRecyclerView.setLayoutManager(mLayoutManager);

第二步、在adapter当中进行判断

@Override
public GalleryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view;
    if (0 == viewType) {
        view = mInflater.inflate(R.layout.vw_gallery_header, parent, false);
    } else if (1 == viewType) {
        view = mInflater.inflate(R.layout.vw_gallery_camera_item, parent, false);
    } else {
        view = mInflater.inflate(R.layout.vw_gallery_item, parent, false);
    }
    view.setTag(viewType);
    return new GalleryViewHolder(view, viewType);
}
@Override
public int getItemViewType(int position) {
    return position;
}

2.2 好了,经过上面两步,我们知道了裁剪区域是怎样覆盖在recyclerView上面的,那么这一步就是体现我们滑动recyclerView时候ScrollLinearLayout如何跟随RecyclerView进行滑动的:关键方法是给recyclerView设置OnScrollListener,让我们来看看这个滑动监听:

//2.初始化mRecyclerOnScrollListener
mRecyclerOnScrollListener = new RecyclerView.OnScrollListener() {
    float limitY = ResHelper.getDimen(R.dimen.crop_image_operation_height);

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (RecyclerView.SCROLL_STATE_IDLE == newState) {
            /*当recyclerview的第一个空白视图的bottom大于 标题的bottom,那么scrollLinearlayout应该滑动到下面*/
            boolean scrollToBottom = false;
            View targetView = mLayoutManager.getChildAt(0);
            if (0 == (int) targetView.getTag()) {
                if (limitY < targetView.getBottom()) {
                    scrollToBottom = true;
                }
            }
            mScrollLinearLayout.clipToBound(mRecyclerView::smoothScrollBy, scrollToBottom);
            System.gc();
        } else {
            if (mPopupWindow != null && mPopupWindow.isShowing()) {
                mPopupWindow.dismiss();
                mPopupWindow = null;
            }
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        View view = mLayoutManager.getChildAt(0);
        if (dy > 0) {
            mScrollLinearLayout.scrollBy((float) mRecyclerView.getLastTouchY(), dx, dy);
        } else {
            if ((Integer) view.getTag() == 1) {
                if (view.getTop() >= 0) {
                    mScrollLinearLayout.scrollBy(0.f, dx, dy);
                }
            }
            if ((Integer) view.getTag() == 0) {
                if (view.getBottom() > limitY) {
                    mScrollLinearLayout.scrollBy(0.f, dx, dy);
                }
            }
        }
    }
};

    根据上面画的流程图再来看现在的这个滑动过程,应该不难理解,这里就不做过多讲解了

2.3 从效果图的动态图,我们可以看到,当选中某个图片的时候,如果ScrollLinearLayout处于顶部悬浮状态的时候此时ScrollLinearLayout会向上滑动,处于初始位置,而recyclerView选中的item则会滑动到ScrollLinearLayout的下面,这个功能是如何实现的呢?recyclerView的item点击事件

//3.初始化recyclerView的点击事件
mOnRecyclerItemClickListener = (position, url, clickView) -> {
    if (1 != position && position == mLastClickPosition) {
        return;
    }
    mLastClickPosition = position;
    //3.0 如果是第一个方框,则需要启动拍照的界面
    if (1 == position && System.currentTimeMillis() - mLastClickTime > 3000) {
        mLastClickTime = System.currentTimeMillis();
        Toast.makeText(GalleryActivity.this, "点击拍照按钮", Toast.LENGTH_SHORT).show();
        return;
    }
    int old = mGalleryAdapter.getSelectedPosition();
    mGalleryAdapter.setSelectedPosition(position);
    mGalleryAdapter.notifyItemChanged(old);
    mGalleryAdapter.notifyItemChanged(position);
    mImageCropView.setImageURI(url);
    //3.1.如果linearLayout是悬浮在上面的,就下滑至原来位置
    mScrollLinearLayout.scrollToBottom();
    //3.2.滑动item到指定位置
    mRecyclerView.smoothScrollBy(0, (int) (clickView.getTop() - mScrollLinearLayout.getFullViewHeight() + 0.5f));
};

    以上就是做到此效果的关键方法,详细可以参考源码功能,这里不做过多介绍
2.4 当ScrollLinearLayout处于顶部悬浮状态的时候,此时,我想ScrollLinearLayout滑下来,只需要点击ScrollLinearLayout露出来的部分就可以了,这是如何做到的呢?就是给三个编辑按钮添加了拦截点击事件.
第一步、GalleryActivity.java

//1.初始化mOnClickInterceptListener
mOnClickInterceptListener = () -> {
    if (mScrollLinearLayout.isTopState()) {
        mScrollLinearLayout.scrollToBottom();
        return true;
    }
    return false;
};

第二步、ImageCropView.java

@OnClick({R.id.image_crop_attach, R.id.image_crop_ratio, 
R.id.image_crop_rotate})
public void onClick(View view) {
    //1.确定是否拦截点击事件
    if (mOnClickInterceptListener != null) {
        if (mOnClickInterceptListener.clickIntercept()) {
            return;
        }
    }
……
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值