IM即时通讯项目讲解(二)--自定义实现图片选择GalleryView

标签(空格分隔): 开源项目


###该系列技术课程来源慕课IM实战

带后台的IM即时通讯App 全程MVP手把手打造

#####通过该课程可以学习到以下知识点

  • 1、了解和开发后台项目(这个是需要长期积累的,有了这个可以说入门没问题)
  • 2、学习到IM相关知识点,创建群、添加群、单聊、群聊
  • 3、可以学习到数据库的相关操作(建表、表之间的关联等知识)
  • 4、学习到MVP模式,更加深入了解MVP模式的架构和实现
  • 5、学习到关于IM相关的优化,比如如何快速刷新界面
  • 6、学习到如何进行推送等相关操作(服务器端推送,单推、群推)
  • 7。。。当然还有好多的,大家不妨去了解一下,学习到知识才是最重要的

###效果图来一发

是不是感觉界面还是挺简洁的呢,那下面就看下如何实现的吧,实现还真不难,反而很简单的。

###前言

项目总结一:实现类似qq微信表情面板无缝切换(简书地址) 实现类似qq微信表情面板无缝切换(CSDN地址)

###进入主题 #####先大致分析一下,主要有以下几点需要我们考虑。

  • 1、如何拿到手机本地图片
  • 2、使用什么控件进行该展示
  • 3、怎么控制照片选择状态
  • 4、要考虑到复用情况,毕竟手机照片可能会有好多张
  • 5、显示出来的是方形(但是加载的图片是形状不规则的)

#####上述问题解决方案

  • 问题一通过LoaderManager和相关类实现
  • 问题二、四
    • 可以看到展示的是四列(可以自己定制),而且每一个图片的展示都市相同的,那么我们可以考虑GridView或者RecyclerView,但是从使用好感上我还是选择了RecyclerView
    • 对于复用的问题,recyclerview也是做了很好的处理,内部强制使用Holder。(这里就不做详细探讨)
  • 问题三
    • 其实这个我们可以放个方式来问,那就是我们的一个item里面都有什么布局。
    • 首先一个ImageView,然后又CheckBox,还有就是点击的阴影效果
  • 问题五--->这个就需要自己定义一个显示方形的控件了,其实就是 重新onMeasure,然后在测量的时候,传入一个依据宽度的值(长和宽都是宽度就行了)

#####好了首先开始写之前的问题,我们都有了相应的解决方案,对于开发中出现的问题我们在遇到的时候当场解决吧。进入实战

###封装实战之前先来看下我们的item布局并附有相关解释 #####首先是方形控件SquareLayout

    //我们选择继承自FrameLayout  重写onMeasure@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//高宽给父类  传递的测量值都是宽度  那么就可以形成基于宽度的正方形控件if (mBaseDirection == 1) {super.onMeasure(widthMeasureSpec, widthMeasureSpec);} else if (mBaseDirection == 2) {super.onMeasure(heightMeasureSpec, heightMeasureSpec);} else {int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);if (heightSize == 0) {super.onMeasure(widthMeasureSpec, widthMeasureSpec);return;}if (widthSize == 0) {super.onMeasure(heightMeasureSpec, heightMeasureSpec);return;}if (widthSize > heightSize)super.onMeasure(heightMeasureSpec, heightMeasureSpec);elsesuper.onMeasure(widthMeasureSpec, widthMeasureSpec);}}复制代码

#####布局控件

<?xml version="1.0" encoding="utf-8"?>
<com.mingchu.common.widget.SquareLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="1dp"android:background="@color/white"android:orientation="vertical"app:comAccordTo="horizontal"><ImageViewandroid:id="@+id/im_image"android:layout_width="match_parent"android:layout_height="match_parent"android:contentDescription="@string/app_name" /><!--black_alpha_112 === #70000000-->><Viewandroid:id="@+id/view_shade"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black_alpha_112"android:visibility="gone" /><CheckBoxandroid:id="@+id/cb_select"android:layout_width="22dp"android:layout_height="22dp"android:layout_gravity="end"android:layout_margin="@dimen/len_2"android:button="@drawable/sel_cb_circle"android:clickable="false"android:drawablePadding="0dp"android:enabled="false"android:padding="0dp"app:buttonTint="@color/cb_gallery" />
</com.mingchu.common.widget.SquareLayout>
复制代码

cb_gallery.xml

<!--white_alpha_192 === #c0ffffff-->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/white" android:state_checked="true" /><item android:color="@color/white_alpha_192" />
</selector>
复制代码

相比没什么好解释的和之前描述的问题解答一样,这里就不多做解释。直接看下面的封装吧

###封装RecyclerView 既然已经选择了RecyclerView来进行我们的本地相片的列表展示,是时候来封装一波RecyclerView,也就是在RecyclerView的基础上自定义view了。 #####开始自定义view的第一步,继承RecyclerView 名字就是GalleryView

 //    代表直接在java代码中引用如setContentView(View)public GalleryView(Context context) {super(context);init();}//    关联中的xml文件中当控件使用public GalleryView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}//    在xml引用,又要自己定义一些属性public GalleryView(Context context, @Nullable AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}复制代码

#####第二步我们先来加载本地图片吧 首先在加载图片之前(为啥来个首先。。。难道还有好多么,哈哈,不是很多,但是你要定义一个bean吧,定义我们需要取哪些数据,哪些字段使我们需要的吧)

      /*** 图片Image   jvabean*/private static class Image {int id;  //数据的idString path;  //图片的路径boolean isSelect; //图片是否选择long date; //图片创建的日期@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Image image = (Image) o;return path != null ? path.equals(image.path) : image.path == null;}@Overridepublic int hashCode() {return path != null ? path.hashCode() : 0;}}复制代码

加载本地并整合到集合中

/*** 用于实际数据加载的Loader*/private class LoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {//读取图片文件的参数private final String[] IMAGE_PROJECTION = {MediaStore.Images.Media._ID,MediaStore.Images.Media.DATA,MediaStore.Images.Media.DATE_ADDED};@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {if (id == LOADER_ID) {return new CursorLoader(getContext(),MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,null, null, IMAGE_PROJECTION[2] + " DESC");}return null;}@Overridepublic void onLoadFinished(Loader<Cursor> loader, final Cursor data) {//当Loader加载完成的时候回调方法List<Image> images = new ArrayList<>();if (data != null) {int count = data.getCount();if (count > 0) {data.moveToFirst();do {//getColumnIndexOrThrow(String columnName)//从零开始返回指定列名称,如果不存在将抛出IllegalArgumentException 异常int id = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));//获取到图片本地地址String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));//获取到照片的时间long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));File file = new File(path);if (!file.exists() || file.length() < MIN_IMAGE_LEN)continue;//构建javabeanImage image = new Image();image.id = id;image.path = path;image.date = dateTime;//添加到集合中images.add(image);} while (data.moveToNext());}}//加载完本地找之后进行更新资源updateSource(images);  }@Overridepublic void onLoaderReset(Loader<Cursor> loader) {//当Loader销毁或者重置updateSource(null);}}复制代码

因为LoaderManager需要配合Activity或者Fragment,所以我们需要对外提供一个方法来传入这两个的实例

/*** 初始化方法** @param manager  LoaderManager Loader管理器* @param listener 选择改变监听* @return 任何一个LOADER_ID  可以用于销毁Loader*/public int setup(LoaderManager manager, SelectedChangeListener listener) {mListener = listener;// 一个标识加载器的唯一ID    一个可选的参数以支持加载器的构建   一个LoaderManager.LoaderCallbacks的实现manager.initLoader(LOADER_ID, null, callback);return LOADER_ID;}
复制代码

相关变量

 private static final int LOADER_ID = 0x0100;private static final long MIN_IMAGE_LEN = 10 * 1024;  //最大的照片的大小  10MBprivate static final long MAX_IMAGE_COUNT = 9;  //最大选择的照片的数量
复制代码

关于这个方法我们在后面会有介绍updateSource(images)

#####图片已经加载到images里面了,该是我们的展示了 无非是写adapter和holder,然后inflater布局,绑定控件,然后设置数据

 private class GalleryAdapter extends RecyclerAdapter<Image> {@Overrideprotected ViewHolder<Image> onCreateViewHolder(View root, int viewType) {return new GalleryView.ViewHolder(root);}@Overrideprotected int getItemViewType(int position, Image image) {return R.layout.cell_gallery;}}private class ViewHolder extends RecyclerAdapter.ViewHolder<Image> {//图片private ImageView mPic;//引用private View mShade;//checkboxprivate CheckBox mSelected;public ViewHolder(View itemView) {super(itemView);mPic = (ImageView) itemView.findViewById(R.id.im_image);mShade = itemView.findViewById(R.id.view_shade);mSelected = (CheckBox) itemView.findViewById(R.id.cb_select);}@Overrideprotected void onBind(Image image) {//加载图片Glide.with(getContext()).load(image.path).diskCacheStrategy(DiskCacheStrategy.NONE).centerCrop().placeholder(R.color.grey_200).into(mPic);//设置选择阴影mShade.setVisibility(image.isSelect ? VISIBLE : INVISIBLE);//是否选择mSelected.setChecked(image.isSelect);//是否显示  未选择的图片checkbox不显示mSelected.setVisibility(image.isSelect ? VISIBLE : INVISIBLE);}
}复制代码

大家肯定会说继承的RecyclerAdapter(还有一个泛型Image是什么鬼),这个不要急,是封装的一个RecyclerView的adapter,这个在文章的结尾会给个地址的(如果篇幅过长,不要打我哈)

 //四列图片setLayoutManager(new GridLayoutManager(getContext(), 4));setAdapter(mAdapter);  //设置adapter
复制代码

这个时候运行一下就是可以出现了哈,但是相关的点击逻辑我们还没有实现哦,现在抓紧时间来实现吧。想法比较简单,逻辑也不复杂哈。

首先是更新数据,也就是loader加载拿到的图片集合后更新数据

  /*** 更新选择的数据** @param images 相册中的图片集合*/private void updateSource(List<Image> images) {mAdapter.replace(images);}复制代码

接下来就是item的点击实现,然后实现选择和未选择的逻辑

 mAdapter.setAdapterItemClickListener(new RecyclerAdapter.AdapterItemClickListener<Image>() {@Overridepublic void onItemClick(RecyclerAdapter.ViewHolder holder, Image image) {if (onItemSelectClick(image)) {//noinspection uncheckedholder.updateData(image);}}@Overridepublic void onLongItemClick(RecyclerAdapter.ViewHolder holder, Image data) {}});
复制代码
 /*** item点击事件逻辑处理** @param image 图片Item* @return true 选择  false 未选择*/private boolean onItemSelectClick(Image image) {boolean notifyRefresh;//判断是否已经选择过了if (mSelectedImages.contains(image)) {//如果选择过了就移除这个imagemSelectedImages.remove(image);//选择标志置为falseimage.isSelect = false;notifyRefresh = true; //需要刷新的标志置为true} else {//判断选择的总共大小是否超出了自定义的可选择大小if (mSelectedImages.size() >= MAX_IMAGE_COUNT) {//Cell点击操作  如果说我们的点击是允许的  那么更新对应的Cell状态//然后去更新界面  如果不允许点击(已经达到我们最大的选择数量)  那么就不需要刷新数据Application.showToast(String.format(getResources().getText(R.string.label_gallery_select_max_size).toString(),MAX_IMAGE_COUNT));//不需要刷新notifyRefresh = false;} else {//如果不在已选择集合中  那么就添加到集合中mSelectedImages.add(image);image.isSelect = true; //选择标志置为truenotifyRefresh = true; //需要通知刷新}}//如果是需要刷新的  添加 或者删除都需要进行刷新if (notifyRefresh)  //通知刷新notifySelectChanged();return notifyRefresh;}复制代码

通知刷新一下

 /*** 通知选择改变的时候刷新*/private void notifySelectChanged() {SelectedChangeListener listener = mListener;if (listener != null)listener.onSelectedCountChanged(mSelectedImages.size());}
复制代码

因为我们有一个最大选择个数,这里定义一个接口,返回我们的选择个数

  /*** 图片选择监听器*/public interface SelectedChangeListener {/*** 选择的个数监听器** @param count 图片个数*/void onSelectedCountChanged(int count);}
复制代码

因为我们最终还需要和界面进行交互,因此我们需要定义一个方法来让外部通过这个方法获取图片地址(简单的就是向外提供选择的图片集合的本地地址)

 /*** 获取到选择过的图片的路径** @return 图片路径集合*/public String[] getSelectedPath() {String[] paths = new String[mSelectedImages.size()];int index = 0;for (Image mSelectedImage : mSelectedImages) {paths[index++] = mSelectedImage.path;}return paths;}
复制代码

好了这里已经实现了,基本上也就是获取本地图片--->封装成我们需要的javabean--->使用recyclerview进行加载--->点击item--->改变item的状态(是否选中,显示checkbox)--->给外部暴露一个获取图片集合路径的方法。好了思路清晰,方法明了。实现也是比较简单。今天就到这了哈。关于安卓实现获取本机的所有图片的方法和解释,这里在参考阅读中给了地址。就不详细描述了。

###想说 鉴于本篇文章已经很长了,这里就不贴全部的代码和封装的recyclerview的代码了,我这里直接提供git地址,这是从一个完整的项目中提取出来的相关总结,大家也可以下载看下,有问题可以讨论。

相册选择自定义View--->GalleryView源码

封装的RecyclerViewAdapter--->RecyclerAdapter

Adapter接口--->AdapterCallBack

###参考阅读

  • Android中Cursor类的概念和用法实例:利用cursor来进行排序

  • Android实现获取本机中所有图片

  • 真实项目运用-RecyclerView封装

  • 封装那些事-RecyclerView封装实践

IM即时通讯项目讲解(二) 自定义实现图片选择GalleryView相关推荐

  1. IM即时通讯项目讲解(一) 实现类似qq微信表情面板无缝切换

    IM即时通讯项目讲解(一)--实现类似qq微信表情面板无缝切换 标签(空格分隔): 开源项目 ###该系列技术课程来源慕课IM实战 带后台的IM即时通讯App 全程MVP手把手打造 #####通过该课 ...

  2. IM即时通讯项目讲解(一)--实现类似qq微信表情面板无缝切换

    该系列技术课程来源慕课IM实战 带后台的IM即时通讯App 全程MVP手把手打造 通过该课程可以学习到以下知识点 1.了解和开发后台项目(这个是需要长期积累的,有了这个可以说入门没问题) 2.学习到I ...

  3. Android-IM从零开始开发一个即时通讯项目

    Android-IM从零开始开发一个即时通讯项目 https://www.jianshu.com/p/dca480006691 关于聊天室项目 聊天室项目,也被称为即时通讯(IM). 其原理是服务器是 ...

  4. Netty实战 IM即时通讯系统(二)Netty简介

    ## Netty实战 IM即时通讯系统(二)Netty简介 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 实战: 客户端和服务端双向通信 数据传输载体ByteBuf ...

  5. 企业即时通讯系统可以二次开发哪些功能

    一.企业即时通讯常见的二次开发能力 日常工作中,沟通如空气般必不可少,因此企业即时通讯系统是常驻在桌面的应用,在操作系统右侧状态栏中的托盘图标闪动能即时的提醒用户有新的消息等待阅读. 随着办公需求的多 ...

  6. 即时通讯项目 java版本qq (含服务器和客户端)源码_即时通讯安卓-QQ互联网和即时通讯云,如何实现即时通讯,这是Android还是Java...

    Android是系统平台. 应用程序所做的是应用程序的开发和完成 也就是说,通信是网络通信,但在手机环境中,网络的情况更复杂,所以我们必须做好结构 安卓即时通讯. 怎么做?安卓版TT即时通讯排行. A ...

  7. agsXMPP + Openfire 即时通讯开发(二) 【文件传输】

    上篇bolg(agsXMPP + Openfire 即时通讯开发(一) [agsXMPP 连接 Openfire])中agsXMPP的MiniClient已经可以相互之间进行通讯了,本篇介绍如何进行文 ...

  8. android选择图片或拍照开源项目,Android 开源照相和图片选择框架PictureSelector

    拍照或者相册选择图片并渲染到UI上,发现了一个更简单的三方框架来实现 -- PictureSelector.Github地址:https://github.com/LuckSiege/PictureS ...

  9. 把TeamTalk(即时通讯项目)中的线程池连接池拆出来单独测试。

    研究过Teamtalk的伙伴会发现它的线程池和连接池与很多文件有关联, 这篇文章主要写,把它的线程池连接池拆出来需要用到哪些文件. 其实我本来只想测试它的连接池的,但发现连接池里套的有线程池,于是就一 ...

  10. GatewayWorker+laravel5.5+layim即时通讯项目demo

    其中一些思路是看一位老铁的老铁demo,感谢这位老铁! https://github.com/shmilylbelva/webim,这位老铁用的是环信,我这里是用GatewayWorker+larav ...

最新文章

  1. Windows 10完美识别3TB硬盘实录
  2. HttpClient 如何设置请求接口等待时间
  3. 人脸识别技术突飞猛进 为应用领域拓展奠定基础
  4. linux swap 内存交换分区调整
  5. Android WebView与js交互通信
  6. gluPerspective的具体含义
  7. 风机桨叶故障诊断(一) 样本的获取
  8. [转]设定version 更新js缓存
  9. StringBuffer和String,StringBuilder的区别?
  10. redis扫描特定keys脚本,可避免阻塞,不影响线上业务
  11. Matlab常用快捷键
  12. javascript简介和基本语法
  13. [力软7.0.6]力软敏捷开发框架前端API
  14. 【精讲】软件工程用图的各个阶段及其应用(详细)系统流程图、数据流图、数据字典、ER图、状态转换图、层次方框图、Warnier图、IPO图、层次图、HIPO图、结构图、程序流程图、盒图等
  15. e480Linux无法发现无线网卡,ThinkPad无线不能用无法连接无线网络的具体排查流程图解...
  16. 2020秋招笔试依图科技编程题
  17. python中function takes exactly_Python error TypeError: function takes exactly 1 argument (5 given)
  18. 大调查:7成网友呼吁共享单车免押金和上保险
  19. 设计师常用的10种平面设计风格,你知道几个?
  20. 支付清算系统简介(1)

热门文章

  1. php 百度地图根据经纬度获取地址,使用百度地图api根据经纬度获取位置
  2. 安装驱动省心办法:驱动总裁
  3. 喜马拉雅下载文件名批量修改
  4. 我的世界光影mod怎么用_我的世界RTX beta版视频体验:仿佛打破了次元壁
  5. 高阶篇:4.2)DFMEA设计失效模式和失效后果分析-总章
  6. python连接sap接口_python 连接SAP的方法
  7. 2022最新版sci和ssci双检索期刊,详细期刊目录值得了解!
  8. AutoCAD启动自动加载程序的研究
  9. 027:vue+openlayers加载远程shp数据(示例代码)
  10. php仿歪歪漫画系统源码