最近由于立项了一个新的项目,一个比较有纪念意义的项目,主打情怀。我也是带着这种深厚的情怀感,非常用心的来写着每一行代码。(假装给领导看下,嘎嘎~)
回归正题,自定义的东西也比较多,上个周翻UI图,忽然看见一个时间轴的相册的东西,当时一看直接用了知乎的一个图片选择器框架,本来想去改进一番,想来想去还是自己写一个吧(其实是看不懂,不太好下手)
我知道没有效果你们是不会往下看的,先上图~
功能再讲一下:
时间轴相册
带浮动日期头部
照片按时间分类
按照相册文件夹分类
可设置最大选择数量
可预览并可选择图片跳转
不同相册选择的图片都会放在一起
首先讲下逻辑,逻辑是这样子的
1,拿到所有的图片和视频(废话)
2,添加listbean,同时区分建立相册文件夹
3,展示出来(完了)
(1,2,3挺简单哈)
----------------------------
1.根据用户瞳孔反光;
2.来获取主题色;
3.更换主题。
以上也是三步,不过这个功能只有他的千分之一(还能还不止)的简单~
下面贴一贴主要代码跟主要逻辑部分,一边看代码一边说
---------------------------------------------------------------华丽的分割线----------------------------------------------------------------
完整的获取类
public class ImageVideoLoader implements LoaderManager.LoaderCallbacks {
private Context mContext;
private AlbumLoadDataCallback mLoader;
public ImageVideoLoader(Context context, AlbumLoadDataCallback loader) {
this.mContext = context;
this.mLoader = loader;
}
String[] MEDIA_PROJECTION = {
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.MEDIA_TYPE,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns._ID,
MediaStore.Video.VideoColumns.DURATION,
MediaStore.Files.FileColumns.PARENT};
@Override
public Loader onCreateLoader(int picker_type, Bundle bundle) {
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
Uri queryUri = MediaStore.Files.getContentUri("external");
CursorLoader cursorLoader = new CursorLoader(
mContext,
queryUri,
MEDIA_PROJECTION,
selection,
null, // Selection args (none).
MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader loader, Object o) {
String allLastDate = "0";
String videoLastDate = "0";
String addFolderLastDate = "0";
String lastFolderName = "";
ArrayList<AlbumFolderBean> albumFolderList = new ArrayList<>();//所有文件夹
AlbumFolderBean allAlbumFolderList = new AlbumFolderBean("所有图片和视频");//当前文件夹中的图片
albumFolderList.add(allAlbumFolderList);
AlbumFolderBean allVideoDir = new AlbumFolderBean("所有视频");
albumFolderList.add(allVideoDir);
Cursor cursor = (Cursor) o;
while (cursor.moveToNext()) {
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
long dateTime = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_ADDED));
int mediaType = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE));
int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
//获取所在的文件夹
String dirName = FileUtils.getParentFolderName(path);
AlbumPhotoInfoBean albumPhotoInfoBean = new AlbumPhotoInfoBean(path, name, dateTime, mediaType, size, id, dirName, 1);
if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
//添加 所有视频
long duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.DURATION));
albumPhotoInfoBean.setDuration(DateUtils.stringForTime(duration));
boolean isToday = DateUtils.isToday(videoLastDate, dateTime + "");
if (!isToday) {
allVideoDir.addMedias(new AlbumPhotoInfoBean(0, dateTime));
videoLastDate = dateTime + "";
allVideoDir.getHeadPositionList().add(allVideoDir.getAlbumFolderList().size() - 1);
}
allVideoDir.addMedias(albumPhotoInfoBean);
}
boolean isToday = DateUtils.isToday(allLastDate, dateTime + "");
if (!isToday) {
//添加头部
allAlbumFolderList.addMedias(new AlbumPhotoInfoBean(0, dateTime));
allLastDate = dateTime + "";
allAlbumFolderList.getHeadPositionList().add(allAlbumFolderList.getAlbumFolderList().size() - 1);
}
//添加所有视频和照片数据
allAlbumFolderList.addMedias(albumPhotoInfoBean);
int index = FileUtils.isExistAlbumDir(albumFolderList, dirName);
if (index != -1) {
//获取上一个保存的文件
AlbumPhotoInfoBean lastBean = albumFolderList.get(index).getAlbumFolderList().get(albumFolderList.get(index).getAlbumFolderList().size() - 1);
String lastDir = lastBean.getParentDir();
//如果不是同一个文件夹 重新定义比较时间
if (!lastDir.equals(lastFolderName)) {
addFolderLastDate = lastBean.getTime() + "";
}
isToday = DateUtils.isToday(addFolderLastDate, dateTime + "");
if (!isToday) {
albumFolderList.get(index).addMedias(new AlbumPhotoInfoBean(0, dateTime));
addFolderLastDate = dateTime + "";
albumFolderList.get(index).getHeadPositionList().add(albumFolderList.get(index).getAlbumFolderList().size() - 1);
}
albumFolderList.get(index).addMedias(albumPhotoInfoBean);
} else {
AlbumFolderBean albumFolderBean = new AlbumFolderBean(dirName);
albumFolderBean.addMedias(new AlbumPhotoInfoBean(0, dateTime));
albumFolderBean.addMedias(albumPhotoInfoBean);
albumFolderBean.getHeadPositionList().add(0);
albumFolderList.add(albumFolderBean);
addFolderLastDate = dateTime + "";
lastFolderName = dirName;
}
}
mLoader.onData(albumFolderList);
cursor.close();
}
@Override
public void onLoaderReset(Loader loader) {
}
}
LoaderManager是个好东西啊,当时主要是为了方便回调,跟activity通过接口做了关联。
在onCreateLoade中来根据selection和MEDIA_PROJECTION通过CursorLoader来查询你想要的数据;
selection为要找的某些信息,比如path,size,duration等等自己要用的信息,只有selection添加了,后面获取的时候才能获取到,否则获取未添加的会报一个错,错误就不贴了,这里提醒一下就可以了。
MEDIA_PROJECTION是要查找的类型(image,video)。
ps(只讲写到的逻辑部分,CursorLoader或者LoaderManager什么的我就不细讲,毕竟我也不会讲....)
cursor.getString(cursor.getColumnIndexOrThrow(这里只能是上边MEDIA_PROJECTION数组中存在的)
cursor中返回的信息好像都是按照时间顺序排好的,这个省了好多时间,不用再次排序了。
手动创建了两个相册文件夹,一个所有图片和视频,一个所有视频,剩下的呢,按照所在的文件夹分类进行了存放,两个bean,一个存放信息,一个存放相册的。头部是在infoBean中做了type区别,用在了adapter中。
ArrayList<AlbumFolderBean> albumFolderList = new ArrayList<>();//所有文件夹
AlbumFolderBean allAlbumFolderList = new AlbumFolderBean("所有图片和视频");//当前文件夹中的图片
albumFolderList.add(allAlbumFolderList);
AlbumFolderBean allVideoDir = new AlbumFolderBean("所有视频");
albumFolderList.add(allVideoDir);
........省略........
int index = FileUtils.isExistAlbumDir(albumFolderList, dirName);
if (index != -1) {//相册文件夹存在,往里添加
........
} else {//不存在,直接创建一个,添加一个日期头部
........
}
这几个变量作用只要是cursor返回的信息并不是按照文件夹一个一个返回的,这边我们存头部类型的时候需要根据上次保存的时间跟上次的保存的文件夹来做对比。
String allLastDate = "0";
String videoLastDate = "0";
String addFolderLastDate = "0";
String lastFolderName = "";
//获取上一个保存的文件
AlbumPhotoInfoBean lastBean = albumFolderList.get(index).getAlbumFolderList().get(albumFolderList.get(index).getAlbumFolderList().size() - 1);
String lastDir = lastBean.getParentDir();
//如果不是同一个文件夹 重新定义比较时间
if (!lastDir.equals(lastFolderName)) {
addFolderLastDate = lastBean.getTime() + "";
}
isToday = DateUtils.isToday(addFolderLastDate, dateTime + "");
if (!isToday) {
albumFolderList.get(index).addMedias(new AlbumPhotoInfoBean(0, dateTime));
addFolderLastDate = dateTime + "";
albumFolderList.get(index).getHeadPositionList().add(albumFolderList.get(index).getAlbumFolderList().size() - 1);
}
albumFolderList.get(index).addMedias(albumPhotoInfoBean);
activity很简单了,就是一个recyclerView,用的GridLayoutManager,有木有人还不知道如何做成一个条目占用一行??
哈哈哈前几天我也不知道,上代码 返回值 你要求的position占几个条目
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (mAlbumShowRvAdapter.getItemViewType(position) == AlbumShowRvAdapter.HEAD_TYPE) {
return gridLayoutManager.getSpanCount();
} else {
return 1;
}
}
});
adapter就是条目类型判断
@Override
public int getItemViewType(int position) {
return mShowItems.get(position).getDataType();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (viewType == HEAD_TYPE) {
return new HeadViewHolder(LayoutInflater.from(mContext).inflate(R.layout.layout_rv_calendar_timeaxis_item_head, null));
} else if (viewType == BODY_TYPE) {
return new BodyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.layout_rv_media_view_item, null));
} else {
return null;
}
}
写到现在简单的东西就出来,只能滑动展示(效果脑补一下,一个带头部跟随滑动的recyclerView)
大家都知道recyclerView可以添加分割线,方式是addItemDecoration,不知道大家知不知道addItemDecoration可以添加多个,浮动的头部就是根据这种方式来的。
继承RecyclerView.ItemDecoration主要看下onDrawOver的操作吧,大致逻辑是获取第一条可见的position来比较下一条position的type是否相同,但是我用的是GridlayoutManager,会存在两个比较的position都在一行的比较问题。所在我在保存图片视频信息的时候,也把每个head所在的对应position值也记录了下来,用当前firstVisiblePosition来查询属于两个头部之间item数量,获取当前时间的条目数量一共多少个(遍历头部取数量,要比把所有图片遍历一边取出对应时间的要快一些)
/**
* //获取当前相同时间的position
* @param position firstVisiblePosition
* @return
*/
public int getCurrentTimeItemCount(int position) {
int count = -1;
for (int i = 0; i < mHeadPositionList.size(); i++) {
if (i + 1 < mHeadPositionList.size()) {
if (position > mHeadPositionList.get(i) && position < mHeadPositionList.get(i + 1)) {
return mHeadPositionList.get(i + 1) - mHeadPositionList.get(i);
}
}
}
return count;
}
这样子就可以把firstVisiblePosition指定成当前可以一行最后一个position来比较下一个position了
int firstVisiblePosition = ((GridLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
int currentTimeItemCount = mAdapter.getCurrentTimeItemCount(firstVisiblePosition) - 1;
if (currentTimeItemCount >= 0) {
int remainder = currentTimeItemCount % ((GridLayoutManager) parent.getLayoutManager()).getSpanCount();
if (remainder == 0) {
firstVisiblePosition = firstVisiblePosition + ((GridLayoutManager) parent.getLayoutManager()).getSpanCount() - 1;
} else if (remainder != 1) {
firstVisiblePosition = firstVisiblePosition + ((GridLayoutManager) parent.getLayoutManager()).getSpanCount() - remainder;
}
}
String time = DateUtils.getSdfTime(allData.get(firstVisiblePosition).getTime(), DateUtils.YMDHMS2);
View child = parent.findViewHolderForLayoutPosition(firstVisiblePosition).itemView;
if (child == null) {
return;
}
boolean flag = false;
if ((firstVisiblePosition + 1) < allData.size()) {
String lastTime = DateUtils.getSdfTime(allData.get(firstVisiblePosition + 1).getTime(), DateUtils.YMDHMS2);
if (!(time + "").equals(lastTime)) {
if (child.getHeight() + child.getTop() < mTitleHight) {
c.save();
flag = true;
c.translate(0, child.getHeight() + child.getTop() - mTitleHight);
}
}
}
View topTitleView = mLayoutInflater.inflate(R.layout.layout_rv_calendar_timeaxis_item_head, null, false);
TextView textView = topTitleView.findViewById(R.id.tv_title);
textView.setText(DateUtils.getSdfTime(allData.get(firstVisiblePosition).getTime(), DateUtils.YMDHMS2));
int toDrawWidthSpec;//用于测量的widthMeasureSpec
int toDrawHeightSpec;//用于测量的heightMeasureSpec
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) topTitleView.getLayoutParams();
if (lp == null) {
lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);//这里是根据复杂布局layout的width height,new一个Lp
topTitleView.setLayoutParams(lp);
}
topTitleView.setLayoutParams(lp);
if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
//如果是MATCH_PARENT,则用父控件能分配的最大宽度和EXACTLY构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY);
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//如果是WRAP_CONTENT,则用父控件能分配的最大宽度和AT_MOST构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST);
} else {
//否则则是具体的宽度数值,则用这个宽度和EXACTLY构建MeasureSpec。
toDrawWidthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
}
//高度同理
if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.EXACTLY);
} else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(), View.MeasureSpec.AT_MOST);
} else {
toDrawHeightSpec = View.MeasureSpec.makeMeasureSpec(mTitleHight, View.MeasureSpec.EXACTLY);
}
//依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
topTitleView.measure(toDrawWidthSpec, toDrawHeightSpec);
topTitleView.layout(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getPaddingLeft() + topTitleView.getMeasuredWidth(), parent.getPaddingTop() + topTitleView.getMeasuredHeight());
topTitleView.draw(c);//Canvas默认在视图顶部,无需平移,直接绘制
if (flag) {
c.restore();//恢复画布到之前保存的状态}
}
再来addItemDecoration一个,新的效果就出来啦~
下面就是预览了,大致讲一下,还是有一些坑要讲的,预览用了viewpager,点击指定的条目,携带选择的list和所有的list,以及当前position跳转,viewpager.setCurrentItem(position),这边就有问题了!!!
这个position是加上了头部head的position,我点的是图片1,但跳转到图片2了, 我........机智想起了之前记录了头部的position位置的集合
//条目点击
@Override
public void onItemClick(AlbumPhotoInfoBean data, int position) {
List<Integer> headPositionList = mAlbumShowRvAdapter.getHeadPositionList();
int count = 0;
for (int i = 0; i < headPositionList.size(); i++) {
if (position > headPositionList.get(i)) {
count++;
} else {
break;
}
}
startPreViewActivity(position - count, mAlbumShowRvAdapter.getAllDataNoHead());
}
看吧,只要点击的索引大于我保存的头部索引,那就+1,最后跳转的时候减到头部数量就好啦~
还有一些选择和预览时选择返回的逻辑我就不一一贴了,没什么坑,就是纯逻辑问题,最后基本就完成了。
最后用起来如下
//跳转 context,maxSelectCount,requestCode
CustomAlbumActivity.startActivity(mvpView.getActivity(), 3, AddVideoInfoActivity.SELECTED_PHOTO_CODE);
//解析结果
public static List<AlbumPhotoInfoBean> obtainPathResult(Intent data) {
List<AlbumPhotoInfoBean> selectList = (List<AlbumPhotoInfoBean>) data.getSerializableExtra(SELECT_LIST_RESULT);
if (selectList == null) {
selectList = new ArrayList<>();
}
return selectList;
}
//接收结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case SELECTED_PHOTO_CODE:
List<AlbumPhotoInfoBean> list = CustomAlbumActivity.obtainPathResult(data);
break;
}
}
}
代码有优化的地方或者其他错误地方希望大家指点指点,android小白,谢谢大家耐心观看~~