该系列技术课程来源慕课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"><ImageView
        android:id="@+id/im_image"android:layout_width="match_parent"android:layout_height="match_parent"android:contentDescription="@string/app_name" /><!--black_alpha_112 === #70000000-->><View
        android:id="@+id/view_shade"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black_alpha_112"android:visibility="gone" /><CheckBox
        android: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. DefaultTableCellRenderer
  2. spring源码分析之cache demo
  3. 61种常用JavaScript
  4. Linux中/proc目录下文件详解(二)
  5. what format should you export from matlab?
  6. wxWidgets 富文本编辑器示例
  7. SQL | 数据分析面试必备SQL语句+语法
  8. MapHack源代码
  9. cocos2d-x初探学习笔记(5)--动作管理
  10. 报名倒计时 | 「TeaTalk」技术沙龙成都站再来袭!
  11. Kotlin——高级篇(四):集合(Array、List、Set、Map)基础
  12. PDFlib TET Crack,文本提取工具包
  13. wsimport指令
  14. 德怀特·艾森豪威尔(1890-1969)美国第34任总统,陆军五星上将。
  15. 如何用发票查验软件快速批量查验发票(返回官网查验截图)
  16. 5G NR协议栈SDAP、PDCP
  17. 春雨(程序员减压儿歌)
  18. 热情起舞小金链尽显墨西哥黑帮气质,这个C4D短片有点辣!
  19. 浅谈德州扑克AI核心算法:CFR
  20. 【渝粤题库】陕西师范大学203001证据法作业(专升本)

热门文章

  1. Java class文件反编译工具推荐以及Eclipse中查看/调试Jar包中的代码
  2. python加载dll后如何查看函数_Python调用DLL带指针函数读写IC卡
  3. CMMI认证多少钱?
  4. 30页不容错过的超赞项目管理PPT
  5. 图元变形lisp源码_CAD lisp 实用小程序(源代码)
  6. 安卓智能手机运行iFIX组态软件
  7. Unity自动修复Sentinel key not found (h0007)错误
  8. java ic卡读写_使用Java驱动ACR122U对IC卡进行读写,总结
  9. PyQt5+爬虫打造磁力链接搜索工具
  10. 挑战程序设计竞赛 练习日记